<?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>Cisco on 0x2142 | Networking Nonsense</title>
    <link>https://0x2142.com/tags/cisco/</link>
    <description>Recent content in Cisco 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/cisco/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>[Quick Fix] Nexus 9k &amp; Ansible: &#34;Request without namespace and filter is an unsupported operation&#34;</title>
      <link>https://0x2142.com/quick-fix-nexus-9k-ansible-netconf/</link>
      <pubDate>Wed, 31 Jan 2024 23:01:48 +0000</pubDate>
      <guid>https://0x2142.com/quick-fix-nexus-9k-ansible-netconf/</guid>
      <description>A quick blog post on how to fix an error with Ansible and Cisco Nexus 9k via NETCONF: &amp;ldquo;Request without namespace and filter is an unsupported operation&amp;rdquo;</description>
      <content:encoded><![CDATA[<p>Hey there everyone, I hope you&rsquo;ve had a great start to the year so far!</p>
<p>In this blog post, I wanted to spend just a few minutes going over how to solve an issue one of my peers ran into recently.</p>
<h2 id="the-issue">The Issue</h2>
<p>Let&rsquo;s say we were trying to use Ansible to update the interface admin state on a Cisco Nexus switch. In this case, I&rsquo;m using a virtual Nexus 9000 which is running NX-OS version 10.2(6).</p>
<p>This switch has a loopback interface (lo100), which we want to disable.</p>
<p>Reading the <a href="https://docs.ansible.com/ansible/latest/collections/ansible/netcommon/netconf_config_module.html#examples">Ansible documentation</a> for the NETCONF module, we might assume that the following would be enough to get the job done:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name </span><span class="p">:</span><span class="w"> </span><span class="l">NX-OS Interface Updates</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">hosts</span><span class="p">:</span><span class="w"> </span><span class="l">all</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">connection</span><span class="p">:</span><span class="w"> </span><span class="l">netconf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">gather_facts</span><span class="p">:</span><span class="w"> </span><span class="kc">no</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tasks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Update Interface State</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ansible.netcommon.netconf_config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">content</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">            &lt;config&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">              &lt;System xmlns=&#34;http://cisco.com/ns/yang/cisco-nx-os-device&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                &lt;intf-items&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                  &lt;lb-items&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &lt;LbRtdIf-list&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                      &lt;id&gt;lo100&lt;/id&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                      &lt;adminSt&gt;down&lt;/adminSt&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &lt;/LbRtdIf-list&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                  &lt;/lb-items&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                &lt;/intf-items&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">              &lt;/System&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">            &lt;/config&gt;</span><span class="w">
</span></span></span></code></pre></div><p>However, once we run that playbook, we&rsquo;ll get an error back:</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>ansible<span class="o">)</span> matt@ansible-dev:~/ansible/nxos$ ansible-playbook nx-update-interface.yaml -i inventory.yml
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">PLAY <span class="o">[</span>NX-OS Interface Updates<span class="o">]</span> **********************************************************************************************************
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">TASK <span class="o">[</span>Update Interface State<span class="o">]</span> ***********************************************************************************************************
</span></span><span class="line"><span class="cl">fatal: <span class="o">[</span>10.122.2.229<span class="o">]</span>: FAILED! <span class="o">=</span>&gt; <span class="o">{</span><span class="s2">&#34;changed&#34;</span>: false, <span class="s2">&#34;msg&#34;</span>: <span class="s2">&#34;Request without namespace and filter is an unsupported operation&#34;</span><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">PLAY RECAP ******************************************************************************************************************************
</span></span><span class="line"><span class="cl">10.122.2.229               : <span class="nv">ok</span><span class="o">=</span><span class="m">0</span>    <span class="nv">changed</span><span class="o">=</span><span class="m">0</span>    <span class="nv">unreachable</span><span class="o">=</span><span class="m">0</span>    <span class="nv">failed</span><span class="o">=</span><span class="m">1</span>    <span class="nv">skipped</span><span class="o">=</span><span class="m">0</span>    <span class="nv">rescued</span><span class="o">=</span><span class="m">0</span>    <span class="nv">ignored</span><span class="o">=</span><span class="m">0</span>
</span></span></code></pre></div><h2 id="the-fix">The Fix</h2>
<p>Luckily, this has an easy solution.</p>
<p>According to the Cisco <a href="https://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus9000/sw/93x/progammability/guide/b-cisco-nexus-9000-series-nx-os-programmability-guide-93x/b-cisco-nexus-9000-series-nx-os-programmability-guide-93x_chapter_0100110.html#id_72426">NX-OS Documentation</a>, there was a change in version 9.3(1) that requires a filter to be sent along with any GET or GET-CONFIG request.</p>
<p>But wait - aren&rsquo;t we updating the config via an EDIT-CONFIG request? We shouldn&rsquo;t need to send a filter, since it&rsquo;s not a GET or GET-CONFIG, right?</p>
<p>Well because of the way Ansible works, we actually send two GET-CONFIG requests along with our EDIT-CONFIG. One <a href="https://github.com/ansible-collections/ansible.netcommon/blob/95d2f6bd66ce5841d951b26790298cf97e3d591b/plugins/modules/netconf_config.py#L622">before</a> the change, and another one <a href="https://github.com/ansible-collections/ansible.netcommon/blob/95d2f6bd66ce5841d951b26790298cf97e3d591b/plugins/modules/netconf_config.py#L673">after</a> making the change.</p>
<p>These are used to validate that we actually updated the config. Ansible uses these two GET-CONFIG requests to compare &amp; see if there is actually any difference between the two.</p>
<p>So to address our error above - when we send our config change in Ansible, we also need to include the get_filter parameter. This will give Ansible a NETCONF filter use when it issues the GET-CONFIG requests to validate our changes.</p>
<p>In our case, that GET-CONFIG filter could look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;System</span> <span class="na">xmlns=</span><span class="s">&#34;http://cisco.com/ns/yang/cisco-nx-os-device&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;intf-items&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;lb-items&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&lt;LbRtdIf-list&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;id&gt;</span>lo100<span class="nt">&lt;/id&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&lt;adminSt/&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&lt;/LbRtdIf-list&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/lb-items&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/intf-items&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/System&gt;</span>
</span></span></code></pre></div><p>That filter would pull back just the admin state for our lo100 interface, which then Ansible could use to compare after we issue our request to disable the interface.</p>
<p>For what it&rsquo;s worth, we don&rsquo;t necessarily have to be that specific with our filter. We just need to ensure that whatever config returned by the filter would also include the section we intend to change.</p>
<p>So for example, we could actually just filter by System and this would still work:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;System</span> <span class="na">xmlns=</span><span class="s">&#34;http://cisco.com/ns/yang/cisco-nx-os-device&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/System&gt;</span>
</span></span></code></pre></div><p>The main difference here is that the switch would return a much larger amount of data, which then Ansible would also need to process to detect the change. So for efficiency &amp; accuracy, it&rsquo;s probably still helpful to keep our get_filter scoped to a reasonably narrow amount.</p>
<p>With all that covered, here&rsquo;s the example of our final playbook with the new filter:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name </span><span class="p">:</span><span class="w"> </span><span class="l">NX-OS Interface Updates</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">hosts</span><span class="p">:</span><span class="w"> </span><span class="l">all</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">connection</span><span class="p">:</span><span class="w"> </span><span class="l">netconf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">gather_facts</span><span class="p">:</span><span class="w"> </span><span class="kc">no</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">tasks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Update Interface State</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">ansible.netcommon.netconf_config</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">get_filter</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">          &lt;System xmlns=&#34;http://cisco.com/ns/yang/cisco-nx-os-device&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                &lt;intf-items&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                  &lt;lb-items&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &lt;LbRtdIf-list&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                      &lt;id&gt;lo100&lt;/id&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                      &lt;adminSt/&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &lt;/LbRtdIf-list&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                  &lt;/lb-items&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                &lt;/intf-items&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">              &lt;/System&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">content</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">            &lt;config&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">              &lt;System xmlns=&#34;http://cisco.com/ns/yang/cisco-nx-os-device&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                &lt;intf-items&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                  &lt;lb-items&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &lt;LbRtdIf-list&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                      &lt;id&gt;lo100&lt;/id&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                      &lt;adminSt&gt;down&lt;/adminSt&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                    &lt;/LbRtdIf-list&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                  &lt;/lb-items&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">                &lt;/intf-items&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">              &lt;/System&gt;
</span></span></span><span class="line"><span class="cl"><span class="sd">            &lt;/config&gt;</span><span class="w">
</span></span></span></code></pre></div><p>And, if we try to run the playbook again:</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>ansible<span class="o">)</span> matt@ansible-dev:~/ansible/nxos$ ansible-playbook nx-update-interface.yaml -i inventory.yml
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">PLAY <span class="o">[</span>NX-OS Interface Updates<span class="o">]</span> **********************************************************************************************************
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">TASK <span class="o">[</span>Update Interface State<span class="o">]</span> ***********************************************************************************************************
</span></span><span class="line"><span class="cl">changed: <span class="o">[</span>10.122.2.229<span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">PLAY RECAP ******************************************************************************************************************************
</span></span><span class="line"><span class="cl">10.122.2.229               : <span class="nv">ok</span><span class="o">=</span><span class="m">1</span>    <span class="nv">changed</span><span class="o">=</span><span class="m">1</span>    <span class="nv">unreachable</span><span class="o">=</span><span class="m">0</span>    <span class="nv">failed</span><span class="o">=</span><span class="m">0</span>    <span class="nv">skipped</span><span class="o">=</span><span class="m">0</span>    <span class="nv">rescued</span><span class="o">=</span><span class="m">0</span>    <span class="nv">ignored</span><span class="o">=</span><span class="m">0</span>
</span></span></code></pre></div><p>That looks much better!</p>
<hr>
<p>Thanks so much for reading this blog post. I really hope it was helpful to you!</p>
<p>If you found value in this, please let me know! Feel free to leave me a comment below, or follow me on <a href="https://www.youtube.com/@0x2142">YouTube</a>!</p>
<p>Oh, and for those of you on Mastodon - You can find me at <a href="https://0x2142.com/@matt">@matt@0x2142.com</a>.</p>
<p>Thanks again!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Getting Started with Cisco YANG Suite</title>
      <link>https://0x2142.com/getting-started-with-cisco-yang-suite/</link>
      <pubDate>Tue, 21 Feb 2023 19:52:12 +0000</pubDate>
      <guid>https://0x2142.com/getting-started-with-cisco-yang-suite/</guid>
      <description>An introduction to setting up Cisco YANG Suite for exploring and testing network device automation via NETCONF &amp;amp; RESTCONF - including an example of automating wireless pre-shared key rotation.</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/nnd4KqeeqIw?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>Back when I started diving into studying for the Cisco DevNet certifications, I thought a lot of the REST API stuff was fairly easy. But I always struggled a bit with wrapping my head around YANG models &amp; how exactly NETCONF/RESTCONF worked. A lot of it seemed more abstract, and browsing through YANG models isn&rsquo;t quite as intuitive as most REST API documentation.</p>
<p>These days I end up working quite a bit with NETCONF - and if there&rsquo;s one tool that&rsquo;s helped me more than anything, it&rsquo;s certainly YANG suite. So in this post, I wanted to spend a little bit of time walking through how to get set up with this tool - and walk through a quick example of how it can be used.</p>
<hr>
<h2 id="whats-yang-suite">What&rsquo;s YANG Suite?</h2>
<p>To put it simply: YANG Suite is a web-based tool that allows you to easily browse through YANG models &amp; find what you need. It also has a bunch of tools to run test queries against devices, which makes developing NETCONF automation easier. It can also auto-generate code snippets - and who doesn&rsquo;t like that? 😄</p>
<p>YANG suite is an open source tool maintained by Cisco &amp; the GitHub repo <a href="https://github.com/CiscoDevNet/yangsuite">here</a>.</p>
<h2 id="initial-setup">Initial Setup</h2>
<p>YANG suite is a containerized application, so we&rsquo;ll need a container host to run it on. You could use something like Docker Desktop to run it on your local PC, but I&rsquo;ll be using a dedicated Ubuntu machine in my lab with Docker installed.</p>
<blockquote>
<p>Note: YANG suite now supports a local Python pip-based install. Check out the <a href="https://github.com/CiscoDevNet/yangsuite">GitHub</a> repo for more info on that.</p></blockquote>
<h3 id="deploy-with-docker">Deploy with Docker</h3>
<p>First thing we&rsquo;ll do is pull down the code from GitHub:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">matt@yangsuite:~/yangsuite/docker$ git clone https://github.com/CiscoDevNet/yangsuite.git
</span></span></code></pre></div><p>Next, we can execute the setup script to help provision the YANG Suite containers, which is located at <code>yangsuite/docker/start_yang_suite.sh</code>. The script will prompt you to set up an admin user and generate self-signed certificates:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">matt@yangsuite:~/yangsuite/docker$ <span class="nb">cd</span> yangsuite/docker
</span></span><span class="line"><span class="cl">matt@yangsuite:~/yangsuite/docker$ ./start_yang_suite.sh
</span></span><span class="line"><span class="cl">Hello, please setup YANG Suite admin user.
</span></span><span class="line"><span class="cl">username: matt
</span></span><span class="line"><span class="cl">password:
</span></span><span class="line"><span class="cl">confirm password:
</span></span><span class="line"><span class="cl">email: matt@0x2142.local
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Setup <span class="nb">test</span> certificates? <span class="o">(</span>y/n<span class="o">)</span>: y
</span></span><span class="line"><span class="cl">  -- output omitted -- 
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">-----
</span></span><span class="line"><span class="cl">You are about to be asked to enter information that will be incorporated
</span></span><span class="line"><span class="cl">into your certificate request.
</span></span><span class="line"><span class="cl">What you are about to enter is what is called a Distinguished Name or a DN.
</span></span><span class="line"><span class="cl">There are quite a few fields but you can leave some blank
</span></span><span class="line"><span class="cl">For some fields there will be a default value,
</span></span><span class="line"><span class="cl">If you enter <span class="s1">&#39;.&#39;</span>, the field will be left blank.
</span></span><span class="line"><span class="cl">-----
</span></span><span class="line"><span class="cl">Country Name <span class="o">(</span><span class="m">2</span> letter code<span class="o">)</span> <span class="o">[</span>AU<span class="o">]</span>: US
</span></span><span class="line"><span class="cl">State or Province Name <span class="o">(</span>full name<span class="o">)</span> <span class="o">[</span>Some-State<span class="o">]</span>:
</span></span><span class="line"><span class="cl">Locality Name <span class="o">(</span>eg, city<span class="o">)</span> <span class="o">[]</span>:
</span></span><span class="line"><span class="cl">Organization Name <span class="o">(</span>eg, company<span class="o">)</span> <span class="o">[</span>Internet Widgits Pty Ltd<span class="o">]</span>:
</span></span><span class="line"><span class="cl">Organizational Unit Name <span class="o">(</span>eg, section<span class="o">)</span> <span class="o">[]</span>:
</span></span><span class="line"><span class="cl">Common Name <span class="o">(</span>e.g. server FQDN or YOUR name<span class="o">)</span> <span class="o">[]</span>:
</span></span><span class="line"><span class="cl">Email Address <span class="o">[]</span>:
</span></span><span class="line"><span class="cl">Certificates generated...
</span></span><span class="line"><span class="cl">Building docker containers...
</span></span></code></pre></div><p>Once all of the above info is provided, the script will build the YANG Suite contianer images &amp; launch them. However, by default it will launch in the foreground - so we can kill the script with <code>Ctrl-C</code> &amp; relaunch the containers with <code>docker-compose up -d</code>.</p>
<h3 id="allow-access-from-remote-ips">Allow Access from Remote IPs</h3>
<p>We may need to make a quick change depending on how we&rsquo;ll be accessing YANG Suite.</p>
<p>By default, YANG Suite will only permit access to the web UI from <code>localhost</code> - so if you&rsquo;re running Docker on your local PC, then no change is necessary.</p>
<p>However, if you&rsquo;re running Docker on a dedicated VM like I am, then we&rsquo;ll need to allow external IPs to access the YANG Suite UI. This is controlled by the <code>DJANGO_ALLOWED_HOSTS</code> environment variable that gets supplied to the container.</p>
<p>As long as we&rsquo;ve already run the setup script once - our environment variables will be saved to <code>yangsuite/docker/yangsuite/setup.env</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">DJANGO_SETTINGS_MODULE</span><span class="o">=</span>yangsuite.settings.production
</span></span><span class="line"><span class="cl"><span class="nv">MEDIA_ROOT</span><span class="o">=</span>/ys-data/
</span></span><span class="line"><span class="cl"><span class="nv">STATIC_ROOT</span><span class="o">=</span>/ys-static/
</span></span><span class="line"><span class="cl"><span class="nv">DJANGO_STATIC_ROOT</span><span class="o">=</span>/ys-static/
</span></span><span class="line"><span class="cl"><span class="nv">DJANGO_ALLOWED_HOSTS</span><span class="o">=</span>localhost
</span></span><span class="line"><span class="cl"><span class="nv">YS_ADMIN_USER</span><span class="o">=</span>&lt;removed&gt;
</span></span><span class="line"><span class="cl"><span class="nv">YS_ADMIN_PASS</span><span class="o">=</span>&lt;removed&gt;
</span></span><span class="line"><span class="cl"><span class="nv">YS_ADMIN_EMAIL</span><span class="o">=</span>&lt;removed&gt;
</span></span></code></pre></div><p>We&rsquo;ll need to change <code>DJANGO_ALLOWED_HOSTS=localhost</code> to the IP address <strong>of the Docker host</strong>. So if your Docker host is reachable at <code>192.168.1.1</code>, then we&rsquo;ll set: <code>DJANGO_ALLOWED_HOSTS=192.168.1.1</code>.</p>
<p>After which, we can bring up the containers again with <code>docker-compose up -d</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">matt@yangsuite:~/yangsuite/docker$ docker-compose up -d
</span></span><span class="line"><span class="cl">Recreating docker-yangsuite-1 ... <span class="k">done</span>
</span></span><span class="line"><span class="cl">Recreating docker-backup-1    ... <span class="k">done</span>
</span></span><span class="line"><span class="cl">Recreating docker-nginx-1     ... <span class="k">done</span>
</span></span></code></pre></div><p>Once everything is running, the web UI can be reached at <code>https://&lt;ip address&gt;:8443</code>.</p>
<h3 id="loading-yang-models">Loading YANG Models</h3>
<p>After we get logged in, we&rsquo;ll need to load the appropriate YANG models for whatever devices we&rsquo;re using.</p>
<p>For example, I&rsquo;ll be using Cisco IOS-XE devices running version 17.6.x code - so I&rsquo;ll need to download the YANG models from: <code>https://github.com/YangModels/yang/tree/main/vendor/cisco/xe/1761</code>.</p>
<p>Within YANG Suite, we&rsquo;ll navigate to <strong>Setup &gt; YANG files and repositories</strong>. Then click the button to add a <strong>New repository</strong>:</p>
<p><img alt="add-yang-repo-name-1" loading="lazy" src="/content/images/2022/12/add-yang-repo-name-1.png#center"></p>
<p>You can name the repo whatever you like. In my case, I named it <code>ios-xe-1761</code> which describes the models it will hold.</p>
<p>Next we can tell YANG Suite where we want to pull our models from. There are a couple of options, including a direct upload from your local PC or querying models directly from a device - but I&rsquo;ll choose <code>Git</code>.</p>
<p>After which, we&rsquo;ll need to fill in the following info:</p>
<p><img alt="add-yang-repo-from-git" loading="lazy" src="/content/images/2022/12/add-yang-repo-from-git.png#center"></p>
<p>The <strong>Repository URL</strong> will be the base Git repo that we&rsquo;re pulling from. In this case, that&rsquo;s <code>https://github.com/YangModels/yang.git</code>.</p>
<p>We&rsquo;ll also need to specify which <strong>Git branch</strong> to pull from, which for the <strong>YangModels</strong> repo will be <code>main</code>.</p>
<p>Then we&rsquo;ll specify which directory to import. The <strong>YangModels</strong> repository holds models for a large number of vendors &amp; devices - so we only want to pull in what is necessary. In my case, I&rsquo;ve set this to <code>vendor/cisco/xe/1761</code> - which will only pull in the YANG models for Cisco IOS-XE 17.6.x devices. I&rsquo;ve also checked the box for <strong>Include subdirectories</strong>.</p>
<p>Once that&rsquo;s all set, we can hit <strong>Import YANG files</strong>. Depending on how many models are being downloaded &amp; how quick your Docker host is, this may take a few minutes.</p>
<p>After it finishes, mine displayed a message showing that 827 models had been added:</p>
<p><img alt="add-yang-repo-complete" loading="lazy" src="/content/images/2022/12/add-yang-repo-complete.png#center"></p>
<h3 id="selecting-yang-module-sets">Selecting YANG Module Sets</h3>
<p>Once we have all of our models downloaded into YANG Suite, it&rsquo;s time to set up some module sets. You can think of these as filters for the YANG models.</p>
<p>For example, later on we&rsquo;ll show how to change a wireless PSK with NETCONF. So since I already know what type of information I&rsquo;ll be working with, I can create a module set to only contain those models.</p>
<p>YANG Suite will require at least one module set to be configured, even if you don&rsquo;t want to filter anything out.</p>
<p>To do this, we&rsquo;ll navigate to <strong>Setup &gt; YANG model sets</strong>. Then click <strong>New YANG set</strong>:</p>
<p><img alt="add-yang-module-set" loading="lazy" src="/content/images/2023/02/add-yang-module-set.png#center"></p>
<p>Again, since we&rsquo;ll be working with wireless data in a bit, I&rsquo;ll name my model set <code>wireless</code>. We&rsquo;ll also have to select which YANG repository this will pull from, which will be <code>ios-xe-1761</code>.</p>
<p>Next, we&rsquo;ll get to select which YANG models we want to include in our module set. To make things easy, I&rsquo;ll filter the models by <code>wireless</code> and then click <strong>Add selected</strong>.</p>
<p><img alt="add-wireless-models" loading="lazy" src="/content/images/2023/02/add-wireless-models.png#center"></p>
<p>Once those are added, you&rsquo;ll likely see an error about missing dependencies. Some of the models we selected may have sub-models or data types that are located in other models.</p>
<p>In order to quickly add these, YANG Suite provides a one-click-button to <strong>Locate and add missing dependencies</strong>:</p>
<p><img alt="add-dependencies" loading="lazy" src="/content/images/2023/02/add-dependencies.png#center"></p>
<p>That&rsquo;s it - our model set is ready to use.</p>
<h3 id="adding-devices">Adding Devices</h3>
<p>Okay, so now that we have all of our necessary YANG models set up, let&rsquo;s take a add a device that we can use for testing. I&rsquo;ll be adding a Catalyst 9800 Wireless controller.</p>
<p>To do this, we&rsquo;ll head over to <strong>Setup &gt; Device Profiles</strong>. Then click on <strong>Create new device</strong>.</p>
<p><img alt="add-device" loading="lazy" src="/content/images/2023/02/add-device.png#center"></p>
<p>Here we&rsquo;ll fill in a name for our device profile, along with the device&rsquo;s IP/FQDN and login credentials.</p>
<p>Scrolling down a bit, we&rsquo;ll also need to make sure we enable the appropriate management protocols. So I&rsquo;ll be checking the box for both NETCONF and RESTCONF, since I have both enabled. I&rsquo;ll also add SSH, which isn&rsquo;t shown in the screenshot below.</p>
<p><img alt="add-device-enable-netconf" loading="lazy" src="/content/images/2023/02/add-device-enable-netconf.png#center"></p>
<p>Please note, you&rsquo;ll likely want to check the box for <em>Skip SSH key validation for this device</em> under the NETCONF settings.</p>
<p>After that&rsquo;s all done, we can click <strong>Create Profile</strong> at the bottom.</p>
<p>Then, to validate everything works - we&rsquo;ll select our device &amp; click the button for <strong>Check selected device&rsquo;s reachability</strong>.</p>
<p>With any luck, you should see a similar result as shown below:</p>
<p><img alt="check-device-reachability" loading="lazy" src="/content/images/2023/02/check-device-reachability.png#center"></p>
<h2 id="example-changing-wireless-psk">Example: Changing Wireless PSK</h2>
<p>Alright, now let&rsquo;s take a look at how we might use YANG Suite to test NETCONF / RESTCONF calls to a wireless controller.</p>
<p>In this example, perhaps we&rsquo;re developing automation to rotate the guest wireless network password on a regular basis. We need to figure out how specifically to accomplish that by finding the right YANG models &amp; understanding what data is sent and received with those calls.</p>
<p>We&rsquo;ll explore how to do this with both NETCONF and RESTCONF.</p>
<h3 id="via-netconf">Via NETCONF</h3>
<p>To start off, we&rsquo;ll navigate to <strong>Protocols &gt; NETCONF</strong>. On this page, we&rsquo;ll have full access to explore models, build XML payloads, and test them against our configured device.</p>
<h4 id="get-config">Get-config</h4>
<p>First thing we&rsquo;ll need is to select our <strong>YANG set</strong> &amp; <strong>Modules</strong>. In my case, the <strong>YANG set</strong> will be <code>wireless</code>, and I&rsquo;ll select the module: <code>Cisco-IOS-XE-wireless-wlan-cfg</code>.</p>
<p>Then click the button to <strong>Load Modules</strong>:</p>
<p><img alt="netconf-select-models" loading="lazy" src="/content/images/2023/02/netconf-select-models.png#center"></p>
<p>By default, our <strong>NETCONF Operation</strong> will be set to <code>get-config</code>, which will work for now since we only want to retrieve configuration and not change anything yet.</p>
<p>Under the <strong>Nodes</strong> pane, we can expand the tree a bit &amp; dig in to find which call we need.</p>
<p>For our example, we&rsquo;ll need to examine the current configured WLAN profiles. So we&rsquo;ll check the box next to <code>wlan-cfg-entries</code>:</p>
<p><img alt="netconf-nodes" loading="lazy" src="/content/images/2023/02/netconf-nodes.png#center"></p>
<p>This will retrieve all configured WLAN profiles on the controller. For now, let&rsquo;s do that so we can figure out what data we&rsquo;ll get back.</p>
<p>We can now click the button to <strong>Build RPC</strong>, and YANG Suite will automatically build the appropriate XML payload for us:</p>
<p><img alt="netconf-build-rpc" loading="lazy" src="/content/images/2023/02/netconf-build-rpc.png#center"></p>
<p>You&rsquo;ll also notice that I selected the <strong>catalyst 9800</strong> as the target device. Let&rsquo;s go ahead and run this with the <strong>Run RPC(s)</strong> button, which should pop open a new window &amp; establish a connection to our device.</p>
<p><img alt="netconf-rpc-response-1" loading="lazy" src="/content/images/2023/02/netconf-rpc-response-1.png#center"></p>
<p>We can see in the response above, that we have two WLAN profiles configured: <code>test-lab-network</code> and <code>test-guest-network</code>. For the purposes of this demo, I have opted to have the guest network PSK stored in plain text, so we can validate our changes more easily.</p>
<p>Okay, so what if we wanted to refine our query XML to only retrieve data on the <code>test-guest-network</code>? Maybe we also want to filter our the additional information returned, and only see the current PSK?</p>
<p>Back on the payload editor, we can expand <code>wlan-cfg-entries</code> and input our desired <code>profile-name</code>:</p>
<p><img alt="netconf-build-rpc-filtered-1" loading="lazy" src="/content/images/2023/02/netconf-build-rpc-filtered-1.png#center"></p>
<p>We&rsquo;ll also find the <code>psk</code> item further down in the list. Now we want to pull back this field, regardless of what the PSK is currently set to. So we&rsquo;ll click the value field, but leave it blank:</p>
<p><img alt="netconf-build-rpc-filtered-psk" loading="lazy" src="/content/images/2023/02/netconf-build-rpc-filtered-psk.png#center"></p>
<p>Once we click <strong>Build RPC</strong>, we can see our payload now shows the additional XML filters asking only for a single profile &amp; it&rsquo;s associated PSK:</p>
<p><img alt="netconf-rpc-payload-filtered" loading="lazy" src="/content/images/2023/02/netconf-rpc-payload-filtered.png#center"></p>
<p>If we run that RPC call, we&rsquo;ll now see that we only get back a limited set of data:</p>
<p><img alt="netconf-filtered-rpc-response" loading="lazy" src="/content/images/2023/02/netconf-filtered-rpc-response.png#center"></p>
<p>Now we know exactly what payload to send if we want to get back the current configured PSK for any specific WLAN profile.</p>
<p>Let&rsquo;s try changing it!</p>
<h4 id="edit-config">Edit-config</h4>
<p>Lucky for us, we&rsquo;re already nearly set up for that. We&rsquo;ll need to make two quick changes. First, set the <strong>NETCONF Operation</strong> to <code>edit-config</code>:</p>
<p><img alt="netconf-op-edit-config" loading="lazy" src="/content/images/2023/02/netconf-op-edit-config.png#center"></p>
<p>Then, under the YANG tree, we&rsquo;ll set a new PSK:</p>
<p><img alt="netconf-new-psk" loading="lazy" src="/content/images/2023/02/netconf-new-psk.png#center"></p>
<p>Again, we&rsquo;ll click <strong>Build RPC</strong> - and take a quick look at the proposed change:</p>
<p><img alt="netconf-new-psk-payload" loading="lazy" src="/content/images/2023/02/netconf-new-psk-payload.png#center"></p>
<p>Look good to you? Okay, let&rsquo;s send it:</p>
<p><img alt="netconf-new-psk-response" loading="lazy" src="/content/images/2023/02/netconf-new-psk-response.png#center"></p>
<p>Sure enough, the new configuration was sent to the wireless controller and we got a response of: <strong>ok</strong>.</p>
<p>But of course, we can check quickly by switching our operation back to <code>get-config</code> and re-running our previous XML payload:</p>
<p><img alt="netconf-new-psk-validation" loading="lazy" src="/content/images/2023/02/netconf-new-psk-validation.png#center"></p>
<h4 id="auto-generated-snippets">Auto-generated snippets</h4>
<p>Well if you&rsquo;re new to NETCONF, you might be asking yourself &ldquo;Okay great, but how do I get this into Python?!?!&rdquo;</p>
<p>YANG Suite has some built in tools to auto-generate Python code or Ansible playbooks, which can help us get started on our code more quickly.</p>
<p>To generate these, click the <strong>Replays</strong> button:</p>
<p><img alt="netconf-python-ansible" loading="lazy" src="/content/images/2023/02/netconf-python-ansible.png#center"></p>
<p>For example, I selected to auto-generate a Python script. Here&rsquo;s what that looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="ch">#! /usr/bin/env python</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">traceback</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">lxml.etree</span> <span class="k">as</span> <span class="nn">et</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">argparse</span> <span class="kn">import</span> <span class="n">ArgumentParser</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">ncclient</span> <span class="kn">import</span> <span class="n">manager</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">ncclient.operations</span> <span class="kn">import</span> <span class="n">RPCError</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">payload</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl"><span class="s1">&#39;&#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="s1">&lt;get-config xmlns=&#34;urn:ietf:params:xml:ns:netconf:base:1.0&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s1">    &lt;source&gt;
</span></span></span><span class="line"><span class="cl"><span class="s1">      &lt;running/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s1">    &lt;/source&gt;
</span></span></span><span class="line"><span class="cl"><span class="s1">    &lt;filter&gt;
</span></span></span><span class="line"><span class="cl"><span class="s1">      &lt;wlan-cfg-data xmlns=&#34;http://cisco.com/ns/yang/Cisco-IOS-XE-wireless-wlan-cfg&#34;&gt;
</span></span></span><span class="line"><span class="cl"><span class="s1">        &lt;wlan-cfg-entries&gt;
</span></span></span><span class="line"><span class="cl"><span class="s1">          &lt;wlan-cfg-entry&gt;
</span></span></span><span class="line"><span class="cl"><span class="s1">            &lt;profile-name&gt;test-guest-network&lt;/profile-name&gt;
</span></span></span><span class="line"><span class="cl"><span class="s1">            &lt;psk/&gt;
</span></span></span><span class="line"><span class="cl"><span class="s1">          &lt;/wlan-cfg-entry&gt;
</span></span></span><span class="line"><span class="cl"><span class="s1">        &lt;/wlan-cfg-entries&gt;
</span></span></span><span class="line"><span class="cl"><span class="s1">      &lt;/wlan-cfg-data&gt;
</span></span></span><span class="line"><span class="cl"><span class="s1">    &lt;/filter&gt;
</span></span></span><span class="line"><span class="cl"><span class="s1">  &lt;/get-config&gt;
</span></span></span><span class="line"><span class="cl"><span class="s1">
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;&#39;&#39;</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></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">parser</span> <span class="o">=</span> <span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s1">&#39;Usage:&#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"># script arguments</span>
</span></span><span class="line"><span class="cl">    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-a&#39;</span><span class="p">,</span> <span class="s1">&#39;--host&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span> <span class="n">required</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">help</span><span class="o">=</span><span class="s2">&#34;Device IP address or Hostname&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-u&#39;</span><span class="p">,</span> <span class="s1">&#39;--username&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span> <span class="n">required</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">help</span><span class="o">=</span><span class="s2">&#34;Device Username (netconf agent username)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;-p&#39;</span><span class="p">,</span> <span class="s1">&#39;--password&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">str</span><span class="p">,</span> <span class="n">required</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">help</span><span class="o">=</span><span class="s2">&#34;Device Password (netconf agent password)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span><span class="s1">&#39;--port&#39;</span><span class="p">,</span> <span class="nb">type</span><span class="o">=</span><span class="nb">int</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="mi">830</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">help</span><span class="o">=</span><span class="s2">&#34;Netconf agent port&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># connect to netconf agent</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">manager</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="n">args</span><span class="o">.</span><span class="n">host</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                         <span class="n">port</span><span class="o">=</span><span class="n">args</span><span class="o">.</span><span class="n">port</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                         <span class="n">username</span><span class="o">=</span><span class="n">args</span><span class="o">.</span><span class="n">username</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">args</span><span class="o">.</span><span class="n">password</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                         <span class="n">timeout</span><span class="o">=</span><span class="mi">90</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                         <span class="n">hostkey_verify</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                         <span class="n">device_params</span><span class="o">=</span><span class="p">{</span><span class="s1">&#39;name&#39;</span><span class="p">:</span> <span class="s1">&#39;csr&#39;</span><span class="p">})</span> <span class="k">as</span> <span class="n">m</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># execute netconf operation</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">rpc</span> <span class="ow">in</span> <span class="n">payload</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">response</span> <span class="o">=</span> <span class="n">m</span><span class="o">.</span><span class="n">dispatch</span><span class="p">(</span><span class="n">et</span><span class="o">.</span><span class="n">fromstring</span><span class="p">(</span><span class="n">rpc</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="n">data</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">xml</span>
</span></span><span class="line"><span class="cl">            <span class="k">except</span> <span class="n">RPCError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">data</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="n">xml</span>
</span></span><span class="line"><span class="cl">                <span class="k">pass</span>
</span></span><span class="line"><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">traceback</span><span class="o">.</span><span class="n">print_exc</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">                <span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1"># beautify output</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">et</span><span class="o">.</span><span class="n">iselement</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">                <span class="n">data</span> <span class="o">=</span> <span class="n">et</span><span class="o">.</span><span class="n">tostring</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">pretty_print</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</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">out</span> <span class="o">=</span> <span class="n">et</span><span class="o">.</span><span class="n">tostring</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="n">et</span><span class="o">.</span><span class="n">fromstring</span><span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">pretty_print</span><span class="o">=</span><span class="kc">True</span>
</span></span><span class="line"><span class="cl">                <span class="p">)</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">            <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">traceback</span><span class="o">.</span><span class="n">print_exc</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">                <span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="n">out</span><span class="p">)</span>
</span></span></code></pre></div><h3 id="via-restconf">Via RESTCONF</h3>
<p>What if we prefer working with REST &amp; JSON instead of XML? This time we&rsquo;ll navigate to <strong>Protocols &gt; RESTCONF</strong>.</p>
<p>Here, we&rsquo;ll select our YANG set &amp; modules again. In case you skipped the NETCONF section, we&rsquo;re working with the <code>wireless</code> YANG set &amp; the <code>Cisco-IOS-XE-wireless-wlan-cfg</code> module.</p>
<p>We&rsquo;ll also select a device to use, then click <strong>Load Module(s)</strong>:</p>
<p><img alt="restconf-nodes" loading="lazy" src="/content/images/2023/02/restconf-nodes.png#center"></p>
<p>Again, we&rsquo;ll see a similar structure to the NETCONF side. But this time, we&rsquo;ll utilize it quite a bit differently.</p>
<p>In the screenshot above, I&rsquo;ve already selected the <code>wlan-cfg-entries</code> entry. Next, we&rsquo;ll click the button to <strong>Generate API(s)</strong>:</p>
<p><img alt="restconf-wlan-all" loading="lazy" src="/content/images/2023/02/restconf-wlan-all.png#center"></p>
<p>We&rsquo;ll see auto-generated API documentation for all of the available calls. This list will contain every variation possible, so it can be quite long. Since we already went through the NETCONF method, we know what data we need to find / use which should make this easier.</p>
<p>To start with, I&rsquo;ll expand the <strong>GET</strong> for <code>/data/Cisco-IOS-XE-wireless-wlan-cfg:wlan-cfg-data/wlan-cfg-entries</code>. We&rsquo;ll have the ability to test the RESTCONF calls directly from this interface as well by clicking on <strong>Try it out</strong>:</p>
<p><img alt="restconf-example-query-wlan" loading="lazy" src="/content/images/2023/02/restconf-example-query-wlan.png#center"></p>
<p>In the screenshot above, I&rsquo;ve already submitted the test request &amp; gotten a response from the controller. While the screenshot is cut off a bit, we can see both of our wireless profile names.</p>
<p>If we wanted to see only the PSK, like we did with our previous example - we could scroll down quite a bit to find the <strong>GET</strong> request for <code>/data/Cisco-IOS-XE-wireless-wlan-cfg:wlan-cfg-data/wlan-cfg-entries/wlan-cfg-entry={wlan-cfg-entry-profile-name}/psk</code></p>
<p>In the screenshot below, I&rsquo;ve already input our <code>test-guest-network</code> profile name &amp; executed the API call:</p>
<p><img alt="restconf-example-query-psk" loading="lazy" src="/content/images/2023/02/restconf-example-query-psk.png#center"></p>
<p>Sure enough, we see the PSK that we had just configured via NETCONF a few minutes ago.</p>
<p>Now let&rsquo;s change it. We&rsquo;ll expand the <strong>PATCH</strong> API call for the same endpoint, and fill in the request body with our new PSK:</p>
<p><img alt="restconf-patch-psk" loading="lazy" src="/content/images/2023/02/restconf-patch-psk.png#center"></p>
<p>Executing that call, the wireless controller returned a status code of <strong>204</strong>.</p>
<p>So let&rsquo;s check again to see if that updated properly:</p>
<p><img alt="restconf-updated-psk" loading="lazy" src="/content/images/2023/02/restconf-updated-psk.png#center"></p>
<p>Now that we have all the appropriate REST calls, it should be much easier to start developing our automation.</p>
<hr>
<p>Okay, I think that&rsquo;s about all I wanted to cover in this post. I&rsquo;ve seen a lot of people struggle with NETCONF much like I used to, and I think the real key to getting familiar is just having the right tools &amp; spending enough time with it. YANG Suite has really helped me get more familiar with the various model structures and how to use them.</p>
<p>Hopefully this information helps others to get started. NETCONF &amp; YANG are certainly big hills to climb, but the right tools can make all the difference.</p>
]]></content:encoded>
    </item>
    <item>
      <title>[How to] Webex Chatbot (Part 2): Digging in to Adaptive Cards</title>
      <link>https://0x2142.com/webex-chatbot-with-adaptivecards/</link>
      <pubDate>Sat, 12 Mar 2022 15:21:35 +0000</pubDate>
      <guid>https://0x2142.com/webex-chatbot-with-adaptivecards/</guid>
      <description>In this post, we&amp;rsquo;ll expand upon the previous Webex weather bot &amp;amp; add in AdaptiveCards to improve user interaction.</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/q4LaBvMePTw?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>A few months ago, I had written a blog post on building a simple chatbot with Webex. You can find that <a href="/how-to-building-a-basic-webex-chatbot/">here</a>.</p>
<p>At the time I was already thinking about some follow-up content, where I could walk through how to use AdaptiveCards to make the bot a little fancier - but I decided to wait a bit &amp; see how the first post performed first.</p>
<p>Well, now it&rsquo;s a few months later &amp; I suppose it&rsquo;s about time I write this 😄.</p>
<p>So in this post - We&rsquo;ll build on the <a href="https://github.com/0x2142/example-scripts/tree/master/simple-webex-chatbot">code</a> I used in the last post. We&rsquo;ll  show how to add cards to both display information, as well as take user input.</p>
<hr>
<h2 id="what-are-adaptivecards">What are AdaptiveCards?</h2>
<p><a href="https://adaptivecards.io/">AdaptiveCards</a> are actually not a Webex/Cisco-specific thing! They were actually created by Microsoft with the intention of being a platform-agnostic way of displaying information. So in theory, a card schema written for a Webex bot could be taken &amp; re-used on another messaging platform that implements AdaptiveCards - without any/much rework.</p>
<p>Hopefully this should mean a more consistent user experience for a bot that is supported across multiple platforms. The cards themselves can also enhance the experience by making your bot more dynamic &amp; interactive. Maybe a developer or network engineer is okay with issuing specific syntax-based commands to a bit - but what about someone who is less computer savvy? Using cards makes it easy to provide guided input with textboxes, buttons, and toggles.</p>
<p>AdaptiveCards are built on a standard JSON structure with a pre-defined <a href="https://adaptivecards.io/explorer/">schema</a> of card properties. Let&rsquo;s take a quick look at a simple example:</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></span><span class="line"><span class="cl">    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;AdaptiveCard&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;$schema&#34;</span><span class="p">:</span> <span class="s2">&#34;http://adaptivecards.io/schemas/adaptive-card.json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;1.2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;body&#34;</span><span class="p">:</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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;TextBlock&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;text&#34;</span><span class="p">:</span> <span class="s2">&#34;Hello there! What&#39;s your name?&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;wrap&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">        <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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Input.Text&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;placeholder&#34;</span><span class="p">:</span> <span class="s2">&#34;Your name here&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;user_name&#34;</span>
</span></span><span class="line"><span class="cl">        <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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;ActionSet&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;actions&#34;</span><span class="p">:</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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Action.Submit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Submit&#34;</span>
</span></span><span class="line"><span class="cl">                <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="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>So in the JSON payload above, we&rsquo;re creating a card that asks for user input. We have a block of text that asks the user &ldquo;What&rsquo;s your name?&rdquo;, then we provide a text input field, and finally a submit button. Looks pretty straightforward, yeah?</p>
<p>We&rsquo;ll get into the specifics around how to create that card JSON in a bit, but you can always use Microsoft&rsquo;s <a href="https://adaptivecards.io/designer">AdaptiveCard Designer</a> to play around with card elements/structure.</p>
<p>So what would that card look like when rendered in Webex? Let&rsquo;s take a look:</p>
<p><img alt="sample-card" loading="lazy" src="/content/images/2022/02/sample-card.png#center"></p>
<p>That card seems a lot more user-friendly than trying to explain to your users that they need to remember some sytax like <code>/botcommand username set &lt;firstname&gt; &lt;lastname&gt;</code>. Instead, they can just enter the info on the card &amp; click submit.</p>
<h2 id="sending-a-static-default-card-from-a-json-file">Sending a Static, Default Card from a JSON File</h2>
<p>So first we&rsquo;ll start off with the easier scenario. We&rsquo;ll have a pre-defined JSON card built, and use the bot to load &amp; send this card response.</p>
<p>One note before we get started - I&rsquo;ll be building on my simple weather chatbot code that I mentioned earlier. If you&rsquo;re following along, you can pull down that code from <a href="https://github.com/0x2142/example-scripts/tree/master/simple-webex-chatbot">here</a>.</p>
<p>Okay, to start off - we&rsquo;ll be creating a card that allows a user to enter their desired zip code. Instead of having to remember the specific command syntax, a user will instead just issue the <code>weather</code> command &amp; our bot will prompt for information via a card.</p>
<p>I&rsquo;ve used the Microsoft Adaptive Card Designer to build out the basic card structure, which I&rsquo;ve kept simple for now. Here&rsquo;s what that looks like:</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></span><span class="line"><span class="cl">    <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;AdaptiveCard&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;$schema&#34;</span><span class="p">:</span> <span class="s2">&#34;http://adaptivecards.io/schemas/adaptive-card.json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;1.2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;body&#34;</span><span class="p">:</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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;TextBlock&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;text&#34;</span><span class="p">:</span> <span class="s2">&#34;Get Current Weather&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;wrap&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;size&#34;</span><span class="p">:</span> <span class="s2">&#34;Medium&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;weight&#34;</span><span class="p">:</span> <span class="s2">&#34;Bolder&#34;</span>
</span></span><span class="line"><span class="cl">        <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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;TextBlock&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;text&#34;</span><span class="p">:</span> <span class="s2">&#34;Enter a Zip code:&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;wrap&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">        <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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Input.Number&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;placeholder&#34;</span><span class="p">:</span> <span class="s2">&#34;00000&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;zip_code&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;min&#34;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;max&#34;</span><span class="p">:</span> <span class="mi">99950</span>
</span></span><span class="line"><span class="cl">        <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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;ActionSet&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;actions&#34;</span><span class="p">:</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="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;Action.Submit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;title&#34;</span><span class="p">:</span> <span class="s2">&#34;Get Weather&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="nt">&#34;callback_keyword&#34;</span><span class="p">:</span> <span class="s2">&#34;weather&#34;</span>
</span></span><span class="line"><span class="cl">                    <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="p">]</span>
</span></span><span class="line"><span class="cl">        <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="p">}</span>
</span></span></code></pre></div><p>This card looks pretty similar to the example I used earlier. We have an initial text block that displays the title &ldquo;Get Current Weather&rdquo;, and we&rsquo;ve added attributes for bold text &amp; a medium size.</p>
<p>Next, we have a normal text block prompting the user to enter their zip code. This is followed by a number input box for that data entry.</p>
<p>The input box has a &ldquo;min&rdquo; &amp; &ldquo;max&rdquo; value for the zip code from 1 to 99950 - which is the full range of US zip codes. We also need to assign any input a unique &ldquo;id&rdquo; value - which we&rsquo;ll see later is used to pull out the user input from the response.</p>
<p>Last, we have our actions - which similar to earlier. This will present as a simple submit button with the text &ldquo;Get Weather&rdquo;. The important note here is adding a <code>data</code> dictionary with a <code>callback_keyword</code> sub-element. We set the <code>callback_keyword</code> to the command we want this submission to be sent to. In our case, we want the input to be sent back to the weather command.</p>
<p>Now to implement. Back in our bot, we&rsquo;ll edit the <code>weather.py</code> file to load the card &amp; assign as the default command response. Let&rsquo;s take a look:</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">### Script snippet</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;./input-card.json&#34;</span><span class="p">,</span> <span class="s2">&#34;r&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">card</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">INPUT_CARD</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">card</span><span class="p">)</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">WeatherByZIP</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">command_keyword</span><span class="o">=</span><span class="s2">&#34;weather&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">help_message</span><span class="o">=</span><span class="s2">&#34;Get current weather conditions by ZIP code.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">card</span><span class="o">=</span><span class="n">INPUT_CARD</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></span><span class="line"><span class="cl"><span class="c1">### Script snippet</span>
</span></span></code></pre></div><p>In the above snippet, I&rsquo;ve extracted just the pieces we&rsquo;ll be editing. For full examples, please see the final example repo <a href="https://github.com/0x2142/example-scripts/tree/master/simple-webex-chatbot-with-cards">here</a>.</p>
<p>I&rsquo;ve saved that JSON payload from earlier as <code>input-card.json</code>. Then we&rsquo;ll open that card file &amp; assign that JSON payload to a variable called <code>INPUT_CARD</code>.</p>
<p>Since we&rsquo;ll expect this card to be the primary method of collecting a zip code - we&rsquo;ll make this card the default response from our bot when the command is triggered. In my original code, we initialized the WeatherByZIP class with <code>card=None</code> - which instead passed our input directly to the <code>execute()</code> function. In this case, we&rsquo;ll set that to <code>card=INPUT_CARD</code> to send our form.</p>
<p>With all that done, let&rsquo;s test it out:</p>
<p><img alt="bot-input" loading="lazy" src="/content/images/2022/03/bot-input.png#center"></p>
<h2 id="collecting--processing-card-submissions">Collecting &amp; Processing Card Submissions</h2>
<p>Now that we have our input card, let&rsquo;s move on to processing the response.</p>
<p>In the original example, we expected the user to enter a command like <code>weather 12345</code> - and it was easy enough to just strip the 5-digit zip code from the response.</p>
<p>Since a card could have many input boxes, we instead receive that data back in a structured JSON format.</p>
<p>If we check out the logging in the webex_bot console, we can see the payload that gets returned to us when the card is submitted:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="mi">2022-03-03</span> <span class="mi">14</span><span class="err">:</span><span class="mi">59</span><span class="err">:</span><span class="mi">26</span>  <span class="p">[</span><span class="err">INFO</span><span class="p">]</span>  <span class="p">[</span><span class="err">webex_websocket_client.root._process_incoming_websocket_message</span><span class="p">]</span><span class="err">:</span><span class="mi">62</span> <span class="err">attachment_actions</span> <span class="err">from</span> <span class="err">message_base_</span><span class="mi">64</span><span class="err">_id:</span> <span class="err">Webex</span> <span class="err">Teams</span> <span class="err">AttachmentAction:</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;--removed--&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;submit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;messageId&#34;</span><span class="p">:</span> <span class="s2">&#34;--removed--&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;inputs&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;callback_keyword&#34;</span><span class="p">:</span> <span class="s2">&#34;weather&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;zip_code&#34;</span><span class="p">:</span> <span class="s2">&#34;12345&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;personId&#34;</span><span class="p">:</span> <span class="s2">&#34;--removed--&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;roomId&#34;</span><span class="p">:</span> <span class="s2">&#34;--removed--&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;created&#34;</span><span class="p">:</span> <span class="s2">&#34;2022-03-03T19:59:24.820Z&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Using this output, we can see exactly what data is passed back to our bot. Under imports, we do see the correct <code>callback_keyword</code> that we defined in our card JSON. But we also see the user input - which is <code>12345</code>. This is listed under the key <code>zip_code</code> which is the <code>id</code> we assigned to our input field earlier.</p>
<p>Now we can easily dig through this response to pull out the data we need. This JSON payload is delivered to the <code>execute</code> function under the <code>attachment_actions</code> parameter.</p>
<p>So we&rsquo;ll make some minor modifications to our original code to pull that value out.</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">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">attachment_actions</span><span class="p">,</span> <span class="n">activity</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">zip_code</span> <span class="o">=</span> <span class="n">attachment_actions</span><span class="o">.</span><span class="n">inputs</span><span class="p">[</span><span class="s1">&#39;zip_code&#39;</span><span class="p">]</span>
</span></span></code></pre></div><p>As a reminder, <code>zip_code</code> is the variable we&rsquo;re using inside the <code>execute()</code> function to store the requested zip code - which is then passed to the OpenWeather API.</p>
<p>Previously, we had just assigned the value of <code>message</code> to <code>zip_code</code>, which was the incoming text message from the user.</p>
<p>Instead, this time we&rsquo;ll fetch the zip code from the incoming card response, using <code>attachment_actions.inputs</code> &amp; pulling out the <code>zip_code</code> key.</p>
<p>With that simple change completed, we should be able to ask the bot for the weather, input our zip code, then receive the same text-based output as before.</p>
<p>Here&rsquo;s what that looks like now:</p>
<p><img alt="bot-input-standard-response-1" loading="lazy" src="/content/images/2022/03/bot-input-standard-response-1.png#center"></p>
<p>As we would expect, upon hitting <code>Get Weather</code> - the bot returns the text-based weather report.</p>
<p>Next, we&rsquo;ll take a look at dynamically building a card to display weather info.</p>
<h2 id="dynamically-building-cards">Dynamically Building Cards</h2>
<p>Now we get to the real fun part. We&rsquo;ll need to dynamically build an AdaptiveCard response, depending on what values we want to present to the user.</p>
<p>I will be using a Python module called <a href="https://github.com/ku222/AdaptiveCardBuilder">adaptivecardbuilder</a>. There are a handful of modules available that can help dynamically build cards, but this was the one I found to be most intuitive for me. While I will walk through an example here, I would also recommend reviewing their docs for additional context/examples.</p>
<p>You could also generate a JSON payload manually, or build one using the card designer &amp; try to edit the JSON dynamically to change card values - but I won&rsquo;t be doing that here.</p>
<p>So we&rsquo;ll start off by installing the adaptive card module:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install adaptivecardbuilder
</span></span></code></pre></div><p>Before we build out the full card response, let&rsquo;s take a quick look at how we can use this module.</p>
<p>In our <code>execute()</code> function, we&rsquo;ll create a simple card that shows what zip code was entered.</p>
<p>First, we&rsquo;ll need to add two new imports:</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">webex_bot.models.response</span> <span class="kn">import</span> <span class="n">Response</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">adaptivecardbuilder</span> <span class="kn">import</span> <span class="o">*</span>
</span></span></code></pre></div><p>We&rsquo;ll import the <code>Response</code> module from <code>webex_bot</code>, which will be required to send any attachments / custom response to the client. We&rsquo;ll also import our <code>adaptivecardbuilder</code> module.</p>
<p>Next, we&rsquo;ll modify the <code>execute()</code> function to look like the following:</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">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">attachment_actions</span><span class="p">,</span> <span class="n">activity</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">zip_code</span> <span class="o">=</span> <span class="n">attachment_actions</span><span class="o">.</span><span class="n">inputs</span><span class="p">[</span><span class="s1">&#39;zip_code&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="n">card</span> <span class="o">=</span> <span class="n">AdaptiveCard</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">TextBlock</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Weather for </span><span class="si">{</span><span class="n">zip_code</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="s2">&#34;Medium&#34;</span><span class="p">,</span> <span class="n">weight</span><span class="o">=</span><span class="s2">&#34;Bolder&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="n">card_data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">card</span><span class="o">.</span><span class="n">to_json</span><span class="p">()))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">card_payload</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;contentType&#34;</span><span class="p">:</span> <span class="s2">&#34;application/vnd.microsoft.card.adaptive&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;content&#34;</span><span class="p">:</span> <span class="n">card_data</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></span><span class="line"><span class="cl">        <span class="n">response</span> <span class="o">=</span> <span class="n">Response</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s2">&#34;Test Card&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">response</span><span class="o">.</span><span class="n">attachments</span> <span class="o">=</span> <span class="n">card_payload</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">response</span>
</span></span></code></pre></div><p>First we create a new <code>AdaptiveCard()</code> instance. With this new object we can add different card elements.</p>
<p>In this case, we add a single card element - a <code>TextBlock</code>. This has multiple attributes, including the actual text content, and we&rsquo;re specifying that we want a medium font size &amp; bold text.</p>
<p>Next we serialize the card payload to JSON using <code>asyncio.run</code>.</p>
<p>In order to prepare the card object to be attached to our response, we will need to set the appropriate headers. In this case we need to wrap the JSON structure with the <code>contentType</code> header. We could use the <code>response.attachments</code> object to send any number of different file types to the user, but in this case we&rsquo;ll set that value to <code>application/vnd.microsoft.card.adaptive</code>.</p>
<p>Then we can assemble the response to the client. We&rsquo;ll create a new instance of the <code>Response()</code> object, where we&rsquo;ll define some necessary attributes.</p>
<p>We&rsquo;ll set our <code>response.text</code> first. This will be the text message sent to the client. If the client supports adaptive cards, then this message won&rsquo;t be shown to the user - however, it may still appear in pop notifications. If the client does not support adaptive cards, only this message will be shown to the user.</p>
<p>Next we attach our complete card payload using <code>response.attachments</code>.</p>
<p>Last but not least, we can <code>return</code> that <code>response</code> object - which is then sent back to the user.</p>
<p><img alt="bot-response-simple-1" loading="lazy" src="/content/images/2022/03/bot-response-simple-1.png#center"></p>
<p>In this case, we can see the bot responds back to our request with a simple card that displays the zip code.</p>
<p>Let&rsquo;s move onto building out the remainder of the card. For the purposes of this example, I&rsquo;ll also be pulling out a few additional items from the OpenWeather API - including sunrise, sunset, pressure, and high/low temperatures.</p>
<p>I&rsquo;ll go ahead and show the full card build 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="n">card</span> <span class="o">=</span> <span class="n">AdaptiveCard</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">TextBlock</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Weather for </span><span class="si">{</span><span class="n">city</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="s2">&#34;Medium&#34;</span><span class="p">,</span> <span class="n">weight</span><span class="o">=</span><span class="s2">&#34;Bolder&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">ColumnSet</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Column</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="s2">&#34;stretch&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">FactSet</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Temp&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">temperature</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Conditions&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">conditions</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Wind&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">wind</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">up_one_level</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">up_one_level</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Column</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="s2">&#34;stretch&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">FactSet</span><span class="p">())</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;High&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">high_temp</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Low&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">low_temp</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Humidity&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">humidity</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">card_data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">card</span><span class="o">.</span><span class="n">to_json</span><span class="p">()))</span>
</span></span></code></pre></div><p>If this looks a little messy, it is. There is a cleaner way to do this, which I&rsquo;ll show in a moment.</p>
<p>The first thing to know is that the card JSON structure is hierarchical. So individual card items need to be added under the correct hierarchy - for example, you create a <code>ColumnSet</code> first, then add a <code>Column</code> underneath, then the items within that Column last.</p>
<p>See the below visualization of this card:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Top Level
</span></span><span class="line"><span class="cl"> ↳ Text Block
</span></span><span class="line"><span class="cl"> ↳ ColumnSet
</span></span><span class="line"><span class="cl">   ↳ Column
</span></span><span class="line"><span class="cl">     ↳ Fact Set
</span></span><span class="line"><span class="cl">       ↳ Fact
</span></span><span class="line"><span class="cl">       ↳ Fact
</span></span><span class="line"><span class="cl">       ↳ Fact
</span></span><span class="line"><span class="cl">    ↳ Column
</span></span><span class="line"><span class="cl">     ↳ Fact Set
</span></span><span class="line"><span class="cl">       ↳ Fact
</span></span><span class="line"><span class="cl">       ↳ Fact
</span></span><span class="line"><span class="cl">       ↳ Fact
</span></span></code></pre></div><p>So on our card, we first placed the title <code>TextBlock</code>. Underneath that we created a <code>ColumnSet</code>, which will contain two <code>Columns</code>.</p>
<p>Each one of those <code>Columns</code> contains a <code>FactSet</code>, which is a key/value pair listing of information. And of course, we add each <code>Fact</code> pair underneath the <code>FactSet</code>.</p>
<p>You&rsquo;ll notice after building one <code>Column</code> and <code>FactSet</code>, we use <code>card.up_one_level()</code> twice - to return back to the <code>ColumnSet</code> level, where we then add a second <code>Column</code>.</p>
<p>This can certainly get a little confusing at first, but I&rsquo;ll show a more intuitive method in a moment.</p>
<p>Let&rsquo;s take a peak at what this looks like now:</p>
<p><img alt="bot-response-full-1" loading="lazy" src="/content/images/2022/03/bot-response-full-1.png#center"></p>
<h2 id="sub-cards--better-card-building">Sub-cards &amp; Better Card Building</h2>
<p>In this section we&rsquo;ll do two quick things. First we&rsquo;ll take a look at a simpler, more visual way of building the cards. Then we&rsquo;ll also expand our example with sub-cards.</p>
<p>The <code>adaptivecardsbuilder</code> module supports a second way of building cards, which I personally find to be much easier to use.</p>
<p>In this method, we just create a single <code>card.add()</code> object - and feed it a list of all the items on the card.</p>
<p>For me, this makes it easier because you can add indentation to each list item - which helps keep track of where you are at in the hierarchical structure.</p>
<p>To navigate within the structure, we use <code>&quot;&lt;&quot;</code> to go up one level, or <code>&quot;^&quot;</code> to return to the top level.</p>
<p>I&rsquo;ve re-written the earlier example with this new structure below:</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">card</span> <span class="o">=</span> <span class="n">AdaptiveCard</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">card</span><span class="o">.</span><span class="n">add</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="n">TextBlock</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;Weather for </span><span class="si">{</span><span class="n">city</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="s2">&#34;Medium&#34;</span><span class="p">,</span> <span class="n">weight</span><span class="o">=</span><span class="s2">&#34;Bolder&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">ColumnSet</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="n">Column</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="s2">&#34;stretch&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">FactSet</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Temp&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">temperature</span><span class="si">}</span><span class="s2">F&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Conditions&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">conditions</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Wind&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">wind</span><span class="si">}</span><span class="s2"> mph&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="s2">&#34;&lt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;&lt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">Column</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="s2">&#34;stretch&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">FactSet</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;High&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">high_temp</span><span class="si">}</span><span class="s2">F&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Low&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">low_temp</span><span class="si">}</span><span class="s2">F&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="s2">&#34;^&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">ActionSet</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">            <span class="n">ActionShowCard</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;More Details&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                <span class="n">ColumnSet</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                    <span class="n">Column</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="s2">&#34;stretch&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">FactSet</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Humidity&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">humidity</span><span class="si">}</span><span class="s2">%&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Pressure&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">pressure</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                            <span class="s2">&#34;&lt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;&lt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">Column</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="s2">&#34;stretch&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                        <span class="n">FactSet</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Sunrise&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">sunrise</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                            <span class="n">Fact</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s2">&#34;Sunset&#34;</span><span class="p">,</span> <span class="n">value</span><span class="o">=</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">sunset</span><span class="si">}</span><span class="s2">&#34;</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="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">card_data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">asyncio</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">card</span><span class="o">.</span><span class="n">to_json</span><span class="p">()))</span>
</span></span></code></pre></div><p>Again, you&rsquo;re welcome to use whichever method suits you - but I find this method to be a bit cleaner &amp; easier to read/troubleshoot.</p>
<p>You can also see that I&rsquo;ve added a new <code>ActionSet</code>, with a single action: <code>ActionShowCard</code>. This adds a nice little button to our card where we can optionally display additional information.</p>
<p>Here&rsquo;s what that looks like now:</p>
<p><img alt="bot-response-full-pt2" loading="lazy" src="/content/images/2022/03/bot-response-full-pt2.png#center"></p>
<p>And if we expand that sub-card:</p>
<p><img alt="bot-response-full-pt2-expanded-2" loading="lazy" src="/content/images/2022/03/bot-response-full-pt2-expanded-2.png#center"></p>
<p>Sub-cards can be an easy way to hide excess information to keep the response clean &amp; focus on the important information.</p>
<hr>
<p>Well that&rsquo;s it for this blog post. There&rsquo;s a lot more you can do with cards, including adding images, video, tables, dropdown menus, etc. But I just wanted to show a few examples of building cards. They can be a simple way to make chatbots a little less boring!</p>
<p>Enjoy!</p>
]]></content:encoded>
    </item>
    <item>
      <title>[How To] Building a Simple Webex Chatbot with Python Websockets &amp; OpenWeather APIs</title>
      <link>https://0x2142.com/how-to-building-a-basic-webex-chatbot/</link>
      <pubDate>Thu, 18 Nov 2021 14:59:53 +0000</pubDate>
      <guid>https://0x2142.com/how-to-building-a-basic-webex-chatbot/</guid>
      <description>Let&amp;rsquo;s build a simple chatbot using Cisco Webex that can query the weather!</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/yZQjoe5XUYE?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>I&rsquo;ve been spending some of my time lately working on a new project that involves an interactive chatbot - where you can send commands to the bot &amp; ask for it to take certain actions or return information.</p>
<p>I didn&rsquo;t previously have a ton of experience building something like this. Mostly what I&rsquo;ve done before is just using Webex or Discord APIs to send alerts or messages to a room.</p>
<p>As with anything new - I&rsquo;ve stumbled my way through a couple of things, but have been enjoying the challenge of trying something different. Building out some of the more interactive parts of the bot hasn&rsquo;t been quite as difficult as I originally imagined, and some of the newer modules &amp; SDKs have made the process much simpler.</p>
<p>With all that being said - I wanted to throw together a quick guide on how to get started building a basic chatbot, in case anyone else is interested but worried that it may be too complicated.</p>
<blockquote>
<p>Repo <a href="https://github.com/0x2142/example-scripts/tree/master/simple-webex-chatbot">here</a> for all the sample code used below</p></blockquote>
<hr>
<h1 id="step-1-getting-api-keys">Step 1: Getting API Keys</h1>
<p>So the first thing we&rsquo;ll worry about is getting signed up for a Webex developer account &amp; generating API keys.</p>
<p>So head on over to the <a href="https://developer.webex.com/">Webex Developer</a> site &amp; log in (or sign up for a new account).</p>
<p>Once logged in, we&rsquo;ll go to the upper-right corner of the screen and click on our user icon - and select &ldquo;My Webex Apps&rdquo; from the drop down:</p>
<p><img alt="01&mdash;login" loading="lazy" src="/content/images/2021/09/01---login.png#center"></p>
<p>Then we&rsquo;ll be asked what type of app we&rsquo;re creating. In this case, we&rsquo;ll select <strong>bot</strong>:</p>
<p><img alt="02&mdash;appselection" loading="lazy" src="/content/images/2021/09/02---appselection.png#center"></p>
<p>Next we&rsquo;ll need to provide some details about our bot. This includes a display name, username, image, and some details. Once the bot is completed, we have the ability to publish it via the Webex App Hub where anyone could interact with it. If you&rsquo;re looking to do this, make sure you give a good description of your bot!</p>
<p><img alt="03&mdash;botdetails" loading="lazy" src="/content/images/2021/09/03---botdetails.png#center"></p>
<p>After we finish all that, we&rsquo;ll finally get our API key!</p>
<p><img alt="04&mdash;apikey" loading="lazy" src="/content/images/2021/09/04---apikey.png#center"></p>
<h1 id="webhook-vs-websocket-which-to-use">Webhook vs Websocket: Which to use?</h1>
<p>Next, I wanted to cover the two primary methods of sending &amp; receiving messages from the Webex APIs: Webhooks and websockets.</p>
<h3 id="webhooks">Webhooks</h3>
<p>Likely webhooks are the method most people are familiar with. Using this method, we need to run out bot code on a publicly reachable URL. When we run our bot, one of our first actions will be sending a request to the Webex APIs with our bot URL. This way, every time Webex receives a message from a user that wants to interact with our bot, the Webex cloud knows where to send that chat message.</p>
<p>Now, the potential downside of using webhooks is that we need to 1) manage a web server and 2) expose the bot publicly to the internet. Both of these could be overcome with easy workarounds, if needed. For example, we could use something like <a href="https://fastapi.tiangolo.com/">FastAPI</a> to quickly build our web listener &amp; handle incoming POST requests. We could also use a tunneling method (like <a href="https://ngrok.com/">ngrok</a>) to avoid having to directly open ports to our webserver.</p>
<h3 id="websockets">Websockets</h3>
<p>On the other hand, we could use websockets to accomplish the same function. With a websocket, we&rsquo;re instead opening a direct, persistent TCP connection to the Webex APIs. Through this persistent connection, we can send &amp; receive messages very quickly and easily.</p>
<p>This method offers a few advantages over webhooks. Primarily not having to expose our app publicly to the internet. Websockets act almost similar to something like ngrok, where we create an outbound tunnel - except in this case, only Webex knows about it and has access to it (where ngrok is still providing you a public URL that anyone could hit, if they knew about it). Additionally, this method removes the need for maintaining a web server &amp; having to handle all of the incoming HTTP requests in your bot&rsquo;s code. There may also be a handful of API calls which are only supported via websockets today as well - like having your bot show that they&rsquo;ve read a message, for example.</p>
<p>I&rsquo;ve also found that the websocket method seems to be much more responsive, likely because there is a persistent TCP connection open - and you no longer have to rely on a full HTTP session establishment &amp; teardown for every message to the bot.</p>
<p>Historically, websockets were only officially supported via the Webex JavaScript SDK. However, it seems recently that someone has built a Python equivalent earlier this year - which we&rsquo;ll be using throughout this blog post. You can find that package <a href="https://github.com/fbradyirl/webex_bot">here</a></p>
<h1 id="getting-started-registering-the-bot-with-webex">Getting started: Registering the Bot with Webex</h1>
<p>So we&rsquo;ll start with some basic functionality. Let&rsquo;s grab the modules we need, apply our API key, and see if we can get a response from our bot.</p>
<p>Luckily, the module we&rsquo;re using makes this super easy. So let&rsquo;s install via pip:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install webex_bot
</span></span></code></pre></div><p>Once that&rsquo;s installed, we&rsquo;ll create a new Python file - I&rsquo;ll call mine <code>bot.py</code></p>
<p>In that file, we&rsquo;ll start our script 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="kn">from</span> <span class="nn">webex_bot.webex_bot</span> <span class="kn">import</span> <span class="n">WebexBot</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">api_token</span> <span class="o">=</span> <span class="s2">&#34;&lt;TOKEN_HERE&gt;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">bot</span> <span class="o">=</span> <span class="n">WebexBot</span><span class="p">(</span><span class="n">api_token</span><span class="p">,</span> <span class="n">approved_domains</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;0x2142.com&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">bot</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</span></span></code></pre></div><p>Looks easy enough, yeah? We&rsquo;ll import our webex_bot module first, as always. Then we&rsquo;ll define our API key. Obviously this can be stored as an environmental variable or another more secure location, but we&rsquo;ll define it here for demonstration.</p>
<p>Then we create a new instance of <code>WebexBot</code>, by providing our API key and an optional <code>approved_domains</code> value. In my case, I&rsquo;ve provided the 0x2142.com domain - which means the bot will reject messages from anyone who isn&rsquo;t from that organization.</p>
<p>Last, we start our bot with <code>bot.run()</code>.</p>
<p>Let&rsquo;s go ahead and run our bot with <code>python bot.py</code> and see what happens!</p>
<p>You should see something similar to this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">matt@0x2142:~/demo-webex-bot$ python bot.py
</span></span><span class="line"><span class="cl">2021-10-25 15:42:52  <span class="o">[</span>INFO<span class="o">]</span>  <span class="o">[</span>webex_bot.webex_bot.webex_bot.__init__<span class="o">]</span>:39 Registering bot with Webex cloud
</span></span><span class="line"><span class="cl">2021-10-25 15:42:52  <span class="o">[</span>INFO<span class="o">]</span>  <span class="o">[</span>restsession.webexteamssdk.restsession.user_agent<span class="o">]</span>:167 User-Agent: webexteamssdk/0+unknown <span class="o">{</span><span class="s2">&#34;implementation&#34;</span>: <span class="o">{</span><span class="s2">&#34;name&#34;</span>: <span class="s2">&#34;CPython&#34;</span>, <span class="s2">&#34;version&#34;</span>: <span class="s2">&#34;3.8.10&#34;</span><span class="o">}</span>, <span class="s2">&#34;system&#34;</span>: <span class="o">{</span><span class="s2">&#34;name&#34;</span>: <span class="s2">&#34;Linux&#34;</span>, <span class="s2">&#34;release&#34;</span>: <span class="s2">&#34;5.10.16.3-microsoft-standard-WSL2&#34;</span><span class="o">}</span>, <span class="s2">&#34;cpu&#34;</span>: <span class="s2">&#34;x86_64&#34;</span><span class="o">}</span>
</span></span><span class="line"><span class="cl">2021-10-25 15:42:52  <span class="o">[</span>WARNING<span class="o">]</span>  <span class="o">[</span>command.webex_bot.models.command.__init__<span class="o">]</span>:33 no card actions data so no entry <span class="k">for</span> <span class="s1">&#39;callback_keyword&#39;</span> <span class="k">for</span> <span class="nb">echo</span>
</span></span><span class="line"><span class="cl">2021-10-25 15:42:52  <span class="o">[</span>INFO<span class="o">]</span>  <span class="o">[</span>command.webex_bot.models.command.set_default_card_callback_keyword<span class="o">]</span>:47 Added default action <span class="k">for</span> <span class="s1">&#39;echo&#39;</span> <span class="nv">callback_keyword</span><span class="o">=</span>callback___echo
</span></span><span class="line"><span class="cl">2021-10-25 15:42:53  <span class="o">[</span>INFO<span class="o">]</span>  <span class="o">[</span>webex_bot.webex_bot.webex_bot.get_me_info<span class="o">]</span>:74 Running as bot <span class="s1">&#39;0x2142 Bot&#39;</span> with email <span class="o">[</span><span class="s1">&#39;0x2142@webex.bot&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">2021-10-25 15:42:54  <span class="o">[</span>INFO<span class="o">]</span>  <span class="o">[</span>webex_websocket_client.root._connect_and_listen<span class="o">]</span>:151 Opening websocket connection to wss://mercury-connection-partition2-a.wbx2.com/v1/apps/wx2/registrations/06b5c858-174a-4977-a268-04530d777563/messages
</span></span><span class="line"><span class="cl">2021-10-25 15:42:54  <span class="o">[</span>INFO<span class="o">]</span>  <span class="o">[</span>webex_websocket_client.root._connect_and_listen<span class="o">]</span>:154 WebSocket Opened.
</span></span></code></pre></div><p>So long as we continue to run this script in the foreground - we&rsquo;ll be able to see the live log of anything that happens, including messages to our bot. So for now, we&rsquo;ll leave this up &amp; running.</p>
<p>Now we should be able to try sending our bot a message in Webex.</p>
<p>In the Webex app, if we do a quick search for our bot (by email or name), we should see it pop up pretty quickly:</p>
<p><img alt="bot" loading="lazy" src="/content/images/2021/10/bot.png#center"></p>
<p>With our new space, we can interact with our bot. I&rsquo;ll send a &ldquo;Hello&rdquo; message:</p>
<p><img alt="bot2" loading="lazy" src="/content/images/2021/10/bot2.png#center"></p>
<p>And the webex_bot module will automatically respond back with a built-in help card.</p>
<p>Currently the bot only supports one command: echo. We&rsquo;ll add our own commands shortly, but for now let&rsquo;s give that a try:</p>
<p><img alt="bot4" loading="lazy" src="/content/images/2021/10/bot4.png#center"></p>
<p>So in the example above, I used the <code>echo</code> command. The bot returns a card with an input field. Once you fill out the textbox &amp; hit submit, the bot will echo back whatever text was provided!</p>
<p>So now we have a running bot. But what about implementing our own commands? Let&rsquo;s take a look!</p>
<h1 id="creating-custom-bot-commands">Creating custom bot commands</h1>
<p><em>Now</em> we get to the fun part!! To demonstrate, let&rsquo;s add a command to ask our bot for the current weather conditions. To accomplish this, we&rsquo;ll use the <a href="https://openweathermap.org/api">OpenWeather</a> APIs.</p>
<p>So to begin - We&rsquo;ll probably want a separate Python file for each command we want to add. First we&rsquo;ll add <code>weather.py</code> to our current directory.</p>
<p>Next, we&rsquo;ll throw in a bit of boilerplate code that will be used for each command:</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">webex_bot.models.command</span> <span class="kn">import</span> <span class="n">Command</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">logging</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">log</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</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">WeatherByZIP</span><span class="p">(</span><span class="n">Command</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">command_keyword</span><span class="o">=</span><span class="s2">&#34;weather&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">help_message</span><span class="o">=</span><span class="s2">&#34;Get current weather conditions by ZIP code.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">card</span><span class="o">=</span><span class="kc">None</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></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">attachment_actions</span><span class="p">,</span> <span class="n">activity</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;Got the message: </span><span class="si">{</span><span class="n">message</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p>So first we&rsquo;ll import <code>Command</code> from our webex_bot module, which will be used to create our custom command class.</p>
<p>Next we create a new class, in this case <code>WeatherByZIP</code>, and inherit the <code>Command</code> class from webex_bot. Within this new class, we&rsquo;ll create our <code>__init__</code> function &amp; define some information about our command.</p>
<p>So using <code>super().__init__()</code>, we&rsquo;ll provide what keyword should trigger this command, any help message to be provided to the user, and an optional card. We saw how the card could work earlier with the <code>echo</code> command - but to keep this module simple, we&rsquo;ll go without a card for now.</p>
<p>After that - we just need to create an <code>execute</code> function. This is what gets called by webex_bot when our command is triggered. By default, webex_bot will pass us the user&rsquo;s message - and, in the case of an action (like a card submission), it will provide those details.</p>
<p>Two things to keep in mind with our <code>execute</code> function:</p>
<ol>
<li>the <code>message</code> variable will contain the user&rsquo;s message with the command keyword removed. So in our case, if the user&rsquo;s message was &ldquo;weather 12345&rdquo; - then the message variable would just contain &ldquo;12345&rdquo;.</li>
<li>Any text we return from our function will be sent via the bot as a chat response. If we wanted to provide extras, like cards or attachments, we would need to use the webex_bot <code>Response</code> object. I might cover this in a separate blog post later</li>
</ol>
<p>Okay! So right now our new command is configured to just echo back any test that is sent to the bot - similar to the <code>echo</code> command earlier, except without the card.</p>
<p>We&rsquo;ll add our weather functionality shortly - but let&rsquo;s first add our command to the bot &amp; give it a quick test.</p>
<p>In our <code>bot.py</code> file, we&rsquo;ll just need to import our command module and call <code>bot.add_command()</code>:</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">webex_bot.webex_bot</span> <span class="kn">import</span> <span class="n">WebexBot</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">weather</span> <span class="kn">import</span> <span class="n">WeatherByZIP</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">api_token</span> <span class="o">=</span> <span class="s2">&#34;&lt;TOKEN_HERE&gt;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">bot</span> <span class="o">=</span> <span class="n">WebexBot</span><span class="p">(</span><span class="n">api_token</span><span class="p">,</span> <span class="n">approved_domains</span><span class="o">=</span><span class="p">[</span><span class="s2">&#34;0x2142.com&#34;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">bot</span><span class="o">.</span><span class="n">add_command</span><span class="p">(</span><span class="n">WeatherByZIP</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">bot</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</span></span></code></pre></div><p>That&rsquo;s it! Now we should be able to try it out. We&rsquo;ll restart our bot, and send the command <code>weather 12345</code>:</p>
<p><img alt="bot5" loading="lazy" src="/content/images/2021/10/bot5.png#center"></p>
<p>Sure enough, our bot gets the message, passes it to our custom command, and returns the stripped message - all as expected.</p>
<p>So let&rsquo;s add our weather query &amp; actually make this thing work.</p>
<p>Using the OpenWeather APIs, I&rsquo;ll create a new API key - then create a variable in the command module called <code>OPENWEATHER_KEY</code>. Then, I&rsquo;ve updated my <code>execute</code> function to 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="k">def</span> <span class="nf">execute</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">attachment_actions</span><span class="p">,</span> <span class="n">activity</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Message may include spaces. Strip whitespace:</span>
</span></span><span class="line"><span class="cl">    <span class="n">zip_code</span> <span class="o">=</span> <span class="n">message</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Define our URL, with requested ZIP code &amp; API Key</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">=</span> <span class="s2">&#34;https://api.openweathermap.org/data/2.5/weather?&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">url</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;zip=</span><span class="si">{</span><span class="n">zip_code</span><span class="si">}</span><span class="s2">&amp;units=imperial&amp;appid=</span><span class="si">{</span><span class="n">OPENWEATHER_KEY</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Query weather</span>
</span></span><span class="line"><span class="cl">    <span class="n">response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">weather</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Pull out desired info</span>
</span></span><span class="line"><span class="cl">    <span class="n">city</span> <span class="o">=</span> <span class="n">weather</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">conditions</span> <span class="o">=</span> <span class="n">weather</span><span class="p">[</span><span class="s1">&#39;weather&#39;</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s1">&#39;description&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">temperature</span> <span class="o">=</span> <span class="n">weather</span><span class="p">[</span><span class="s1">&#39;main&#39;</span><span class="p">][</span><span class="s1">&#39;temp&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">humidity</span> <span class="o">=</span> <span class="n">weather</span><span class="p">[</span><span class="s1">&#39;main&#39;</span><span class="p">][</span><span class="s1">&#39;humidity&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">wind</span> <span class="o">=</span> <span class="n">weather</span><span class="p">[</span><span class="s1">&#39;wind&#39;</span><span class="p">][</span><span class="s1">&#39;speed&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">response_message</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;In </span><span class="si">{</span><span class="n">city</span><span class="si">}</span><span class="s2">, it&#39;s currently </span><span class="si">{</span><span class="n">temperature</span><span class="si">}</span><span class="s2">F with </span><span class="si">{</span><span class="n">conditions</span><span class="si">}</span><span class="s2">. &#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">response_message</span> <span class="o">+=</span> <span class="sa">f</span><span class="s2">&#34;Wind speed is </span><span class="si">{</span><span class="n">wind</span><span class="si">}</span><span class="s2">mph. Humidity is </span><span class="si">{</span><span class="n">humidity</span><span class="si">}</span><span class="s2">%&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">response_message</span>
</span></span></code></pre></div><p>So because of the way that <code>message</code> is passed to us, we&rsquo;ll first need to strip whitespace. This is because when the user enters a command like &ldquo;weather 12345&rdquo;, webex_bot will only remove the &ldquo;weather&rdquo; portion - leaving us with &quot; 12345&quot;. So by stripping that value, we&rsquo;re left with just the numeric ZIP Code: &ldquo;12345&rdquo;.</p>
<p>Next we assemble our URL, supplying our parameters: zip, units, and appid. Zip is the numeric ZIP code provided by our user, units will be either imperial or metric, and appid is our API key.</p>
<p>After that, it&rsquo;s as simple as sending the GET request &amp; parsing the data. In the code above, I assemble the final text response into <code>response_message</code> which is then returned to the user.</p>
<p>All that being done - Let&rsquo;s try it out!!</p>
<p><img alt="bot6" loading="lazy" src="/content/images/2021/10/bot6.png#center"></p>
<p>In this example, I used the ZIP code for Richfield, OH - and our bot quickly returned that information to us.</p>
<hr>
<p>And that&rsquo;s it! In just a few steps we created a new Webex bot &amp; added custom commands. Once we have that process down, it&rsquo;s easy enough to continue extending our bot by adding additional commands.</p>
<p>There are obviously more advanced use cases - and a whole lot of <em>fun</em> that comes with using Adaptive Cards&hellip; But I hope this post was helpful in getting you started!</p>
<p><strong>Update 2022/03/12 -</strong> If you&rsquo;re interested in seeing additional content around Cards, please check out the new blog post &amp; video <a href="/webex-chatbot-with-adaptivecards/">here</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>[How To] Cisco SD-WAN - Onboarding a Catalyst 8000v</title>
      <link>https://0x2142.com/how-to-cisco-sd-wan-onboarding-a-catalyst-8000v/</link>
      <pubDate>Fri, 14 May 2021 16:10:38 +0000</pubDate>
      <guid>https://0x2142.com/how-to-cisco-sd-wan-onboarding-a-catalyst-8000v/</guid>
      <description>Let&amp;rsquo;s look at how to join a Cisco Catalyst 8000v to an SDWAN network</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/HyPYLKrPPsk?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>I&rsquo;ve been getting a handful of questions lately on the process of bringing a Cisco Catalyst 8000v or CSR 1000v into an SD-WAN environment. So I figured maybe I should put something together to share.</p>
<p>On a related note - I&rsquo;ve been debating for a while doing a blog and/or video on building out a Cisco Viptela SD-WAN lab in EVE-NG. This would (tentatively) include everything from building controllers, bringing up remote sites, and template/policy configs.  If this is something you might get value from, please let me know!! I&rsquo;m looking for some motivation :)</p>
<p>Okay - all that being said, let&rsquo;s go ahead and get started.</p>
<blockquote>
<p>Note: This guide should work with CSR 1000v devices as well. But it will NOT be 100% accurate for physical ISR/IOS-XE routers, as there are some additional steps with certificates &amp; the Plug and Play portal to get those running.</p></blockquote>
<hr>
<h2 id="topology">Topology</h2>
<p>So to start with, figured I would share the topology that I&rsquo;m working from. If you read my <a href="/cisco-sdwan-and-umbrella-sig-integration/">last post</a> you might recognize this, but with an added location. This new location (site id 400) contains our Catalyst 8000v VM, running IOS-XE version 17.04.01a.</p>
<p><img alt="001&mdash;Topology" loading="lazy" src="/content/images/2021/05/001---Topology.PNG#center"></p>
<h2 id="controller-or-autonomous-mode">Controller or Autonomous Mode?</h2>
<p>Back in the earlier days of IOS-XE SD-WAN, there used to be two separate software images to load on your network appliance - one for traditional IOS-XE, and one for SD-WAN code.</p>
<p>With the newer releases of IOS-XE, we&rsquo;re now getting a unified image that contains both software sets. So our options now are two modes: autonomous (traditional IOS-XE) or controller (SD-WAN).</p>
<p>One way we can check this, is by running a <em>show version</em> and looking for <strong>Router operating mode</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Router# show version
</span></span><span class="line"><span class="cl">&lt;-- Output omitted --&gt;
</span></span><span class="line"><span class="cl">cisco C8000V (VXE) processor (revision VXE) with 2035355K/3075K bytes of memory.
</span></span><span class="line"><span class="cl">Processor board ID XXXXXXXXXXX
</span></span><span class="line"><span class="cl">Router operating mode: Autonomous
</span></span><span class="line"><span class="cl">4 Gigabit Ethernet interfaces
</span></span><span class="line"><span class="cl">32768K bytes of non-volatile configuration memory.
</span></span><span class="line"><span class="cl">3965744K bytes of physical memory.
</span></span><span class="line"><span class="cl">5234688K bytes of virtual hard disk at bootflash:.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Configuration register is 0x2102
</span></span></code></pre></div><p>Okay, and looks like the Catalyst 8000v image I&rsquo;m using booted up in autonomous mode. No big deal, we can change modes pretty easily!</p>
<p>So in normal exec-mode, we&rsquo;ll use the command <em>controller-mode enable</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Router# controller-mode enable
</span></span><span class="line"><span class="cl">Enabling controller mode will erase the nvram filesystem, remove all configuration files, and reload the box!
</span></span><span class="line"><span class="cl">Ensure the BOOT variable points to a valid image
</span></span><span class="line"><span class="cl">Continue? [confirm]
</span></span></code></pre></div><p>As noted in the snippet above - this command will erase the config! So you will want to take a backup snapshot of your existing configuration, if this is already being used.  In my case, it&rsquo;s a brand new VM - so we&rsquo;ll continue on.</p>
<p>Once the device is back online, we&rsquo;ll log in with the default login of <strong>admin/admin</strong>. Note that you will be forced to change this upon first login.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">User Access Verification
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Username: admin
</span></span><span class="line"><span class="cl">Password:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Default admin password needs to be changed.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Enter new password:
</span></span><span class="line"><span class="cl">Confirm password:
</span></span><span class="line"><span class="cl">Router#
</span></span></code></pre></div><p>And just for fun, we&rsquo;ll run a <em>show version</em> again to ensure we&rsquo;re in controller mode:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Router# show version 
</span></span><span class="line"><span class="cl">&lt;-- Output omitted --&gt;
</span></span><span class="line"><span class="cl">cisco C8000V (VXE) processor (revision VXE) with 2035355K/3075K bytes of memory.
</span></span><span class="line"><span class="cl">Processor board ID XXXXXXXXXXX
</span></span><span class="line"><span class="cl">Router operating mode: Controller-Managed
</span></span><span class="line"><span class="cl">4 Gigabit Ethernet interfaces
</span></span><span class="line"><span class="cl">32768K bytes of non-volatile configuration memory.
</span></span><span class="line"><span class="cl">3965756K bytes of physical memory.
</span></span><span class="line"><span class="cl">5234688K bytes of virtual hard disk at bootflash:.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Configuration register is 0x2102
</span></span></code></pre></div><h2 id="catalyst-8000v-initial-config">Catalyst 8000v Initial Config</h2>
<p>So since this is a lab environment, and I&rsquo;m not trying to provide any Day0 provisioning files - I&rsquo;ll have to complete some manual configuration to get started.</p>
<p>First, we&rsquo;ll start with some SD-WAN specific config (site id, org name, etc). I&rsquo;ll be using the values that apply to my lab, so be sure to change these in yours!</p>
<blockquote>
<p>Note: If you haven&rsquo;t used IOS-XE SD-WAN previously, be aware that &ldquo;conf t&rdquo; doesn&rsquo;t work! In SD-WAN/controller mode, you&rsquo;ll use &ldquo;config-transaction&rdquo;. In this mode, all changes will need to be committed before they&rsquo;re applied to the device.</p></blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Router# config-transaction
</span></span><span class="line"><span class="cl"> ! Set hostname
</span></span><span class="line"><span class="cl">Router(config)# hostname Cat8k-Site400
</span></span><span class="line"><span class="cl"> ! Required SD-WAN system configs
</span></span><span class="line"><span class="cl">Router(config)# system
</span></span><span class="line"><span class="cl">Router(config-system)# organization-name &#34;SDWAN-LAB&#34;
</span></span><span class="line"><span class="cl">Router(config-system)# site-id 400
</span></span><span class="line"><span class="cl">Router(config-system)# vbond 192.168.99.241
</span></span><span class="line"><span class="cl">Router(config-system)# system-ip 10.10.10.237
</span></span><span class="line"><span class="cl">Router(config-system)# commit
</span></span><span class="line"><span class="cl">Commit complete.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Cat8k-Site400# 
</span></span></code></pre></div><p>Once we have those basics configured, at a minimum we&rsquo;ll also need to configure our internet-facing tunnel interface. This allows our Catalyst 8000v to communicate with our control plane for bring-up.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Cat8k-Site400#config-transaction
</span></span><span class="line"><span class="cl"> ! Config physical interface (This is a lab, so I&#39;m using a static IP)
</span></span><span class="line"><span class="cl">Cat8k-Site400(config)# interface GigabitEthernet1
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-if)# ip address 192.168.99.237 255.255.255.0
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-if)# no shut
</span></span><span class="line"><span class="cl"> ! Config tunnel interface
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-if)# interface Tunnel1
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-if)# no shut
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-if)# ip unnumbered GigabitEthernet1
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-if)# tunnel source GigabitEthernet1
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-if)# tunnel mode sdwan
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-if)# exit
</span></span><span class="line"><span class="cl"> ! SD-WAN tunnel config
</span></span><span class="line"><span class="cl">Cat8k-Site400(config)# sdwan
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-sdwan)# interface GigabitEthernet1
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-interface-GigabitEthernet1)# tunnel-interface
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-tunnel-interface)# encapsulation ipsec
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-tunnel-interface)# color biz-internet
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-tunnel-interface)# exit
</span></span><span class="line"><span class="cl"> ! Default route to our internet gateway
</span></span><span class="line"><span class="cl">Cat8k-Site400(config)# ip route 0.0.0.0 0.0.0.0 192.168.99.1
</span></span><span class="line"><span class="cl">Cat8k-Site400(config)# commit
</span></span><span class="line"><span class="cl">Commit complete.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Cat8k-Site400#
</span></span></code></pre></div><h2 id="a-note-on-certificates">A Note on Certificates</h2>
<p>Since this is a lab environment, I&rsquo;m using self-signed local certificate authority to provision all of my certificate infrastructure. Because of this, I&rsquo;ll need to install my local CA certificate on the Catalyst 8000v. If you&rsquo;re using the default Cisco-provisioned certificate setup, you won&rsquo;t need to do this.</p>
<p>Since my SD-WAN lab doesn&rsquo;t have direct access to my local TFTP server - I do have an out-of-band management interface connected to my Cat 8000v. We&rsquo;ll start by configuring that interface:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Cat8k-Site400# config-transaction
</span></span><span class="line"><span class="cl">Cat8k-Site400(config)# vrf definition 512
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-vrf)# address-family ipv4
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-vrf)# exit
</span></span><span class="line"><span class="cl">Cat8k-Site400(config)# interface GigabitEthernet 3
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-if)# vrf forwarding 512
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-if)# ip address dhcp
</span></span><span class="line"><span class="cl">Cat8k-Site400(config-if)# exit
</span></span><span class="line"><span class="cl">Cat8k-Site400(config)# ip tftp source-interface GigabitEthernet 3
</span></span><span class="line"><span class="cl">Cat8k-Site400(config)# commit
</span></span></code></pre></div><p>The standard management VRF/VPN for SD-WAN is 512, so I kept that config to match when I configured this management interface. This will all be over-written anyways once we get connected to vManage &amp; configure/push our template configs.</p>
<p>Once that&rsquo;s done, we can go ahead and copy our CA certificate to bootflash.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Cat8k-Site400# copy tftp://10.0.0.2/cacert.pem bootflash:
</span></span><span class="line"><span class="cl">Destination filename [cacert.pem]?
</span></span><span class="line"><span class="cl">Accessing tftp://10.0.0.2/cacert.pem...
</span></span><span class="line"><span class="cl">Loading cacert.pem from 10.0.0.2 (via GigabitEthernet3): !
</span></span><span class="line"><span class="cl">[OK - 1406 bytes]
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">1406 bytes copied in 0.112 secs (12554 bytes/sec)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Cat8k-Site400# dir bootflash: | inc cacert.pem
</span></span><span class="line"><span class="cl">16      -rw-             1406  May 14 2021 14:41:27 +00:00  cacert.pem
</span></span><span class="line"><span class="cl">```text
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Then we&#39;ll use the command below to install the CA certificate:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">```text
</span></span><span class="line"><span class="cl">Cat8k-Site400# request platform software sdwan root-cert-chain install bootflash:cacert.pem
</span></span><span class="line"><span class="cl">Uploading root-ca-cert-chain via VPN 0
</span></span><span class="line"><span class="cl">Copying ... /bootflash/cacert.pem via VPN 0
</span></span><span class="line"><span class="cl">Updating the root certificate chain..
</span></span><span class="line"><span class="cl">Successfully installed the root certificate chain
</span></span></code></pre></div><p>And we can validate by using the <em>show sdwan certificate root-ca-cert</em> command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Cat8k-Site400 show sdwan certificate root-ca-cert
</span></span><span class="line"><span class="cl">Certificate:
</span></span><span class="line"><span class="cl">    Data:
</span></span><span class="line"><span class="cl">        Version: 3 (0x2)
</span></span><span class="line"><span class="cl">        Serial Number:
</span></span><span class="line"><span class="cl">            11:3b:0a:12:17:b0:e0:b5:4b:fa:c2:e9:2c:9c:12:84
</span></span><span class="line"><span class="cl">        Signature Algorithm: sha1WithRSAEncryption
</span></span><span class="line"><span class="cl">        Issuer: DC = local, DC = 0x2142, O = SDWAN-LAB, CN = 0x2142-0XWIN1-CA-2
</span></span><span class="line"><span class="cl">        Validity
</span></span><span class="line"><span class="cl">            Not Before: Nov 29 20:00:11 2018 GMT
</span></span><span class="line"><span class="cl">            Not After : Nov 29 20:10:11 2043 GMT
</span></span><span class="line"><span class="cl">        Subject: DC = local, DC = 0x2142, O = SDWAN-LAB, CN = 0x2142-0XWIN1-CA-2
</span></span><span class="line"><span class="cl">        Subject Public Key Info:
</span></span><span class="line"><span class="cl">            Public Key Algorithm: rsaEncryption
</span></span><span class="line"><span class="cl">                RSA Public-Key: (2048 bit)
</span></span><span class="line"><span class="cl">                Modulus:
</span></span><span class="line"><span class="cl">        &lt;-- Output omitted --&gt;
</span></span></code></pre></div><h2 id="activating-the-catalyst-8000v">Activating the Catalyst 8000v</h2>
<p>Almost there! Now that we&rsquo;ve finished our pre-config &amp; added our root CA certificate - we&rsquo;re ready to join the Catalyst 8000v to the SD-WAN fabric.</p>
<p>We&rsquo;ll start over in vManage - by going to <strong>Configuration &gt; Devices</strong>.</p>
<p>Then we&rsquo;ll find our target, unused Catalyst 8000v device. Click the ellipsis on the right side, then select <strong>Generate Bootstrap Configuration</strong></p>
<p><img alt="002&mdash;Generate-bootstap-config" loading="lazy" src="/content/images/2021/05/002---Generate-bootstap-config.png#center"></p>
<p>This will give us a prompt to select which configuration style to generate. We&rsquo;ll leave this on &ldquo;Cloud-init&rdquo;:</p>
<p><img alt="003&mdash;bootstrap" loading="lazy" src="/content/images/2021/05/003---bootstrap.PNG#center"></p>
<p>Once we hit okay - we&rsquo;ll be presented with the info we need. We won&rsquo;t necessarily need all of this information, but we&rsquo;ll want to take note of our <strong>uuid</strong> and <strong>otp</strong>:</p>
<p><img alt="004&mdash;otp" loading="lazy" src="/content/images/2021/05/004---otp.png#center"></p>
<p>We&rsquo;ll drag this info back over to our Catalyst 8000v, and we can now use it to activate the device &amp; join to our SD-WAN fabric.</p>
<p>For the command below, <strong>chassis-number</strong> will be our <strong>uuid</strong> value - and <strong>token</strong> will be our <strong>otp</strong>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Cat8k-Site400# request platform software sdwan vedge_cloud activate chassis-number C8K-178BXXXX-XXXX-XXXX-XXXX-XXXXXXXXBC24 token 421ecxxxxxxxxxxxxxxxxxxxxxbd53b5
</span></span></code></pre></div><h2 id="validation">Validation</h2>
<p>After a few moments, we&rsquo;ll see some log messages start to appear showing our control connections coming up. You might see these on the terminal if you&rsquo;re using the console port, or you can use <em>show log</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">*May 14 15:39:06.910: %Cisco-SDWAN-Cat8k-Site400-OMPD-3-ERRO-400002: R0/0: OMPD: vSmart peer 10.10.10.242 state changed to Init
</span></span><span class="line"><span class="cl">*May 14 15:39:07.980: %DMI-5-AUTH_PASSED: R0/0: dmiauthd: User &#39;vmanage-admin&#39; authenticated successfully from 10.10.10.240:48140 and was authorized for netconf over ssh. External groups:
</span></span><span class="line"><span class="cl">*May 14 15:39:08.798: %Cisco-SDWAN-Cat8k-Site400-OMPD-6-INFO-400002: R0/0: OMPD: vSmart peer 10.10.10.242 state changed to Handshake
</span></span><span class="line"><span class="cl">*May 14 15:39:08.804: %Cisco-SDWAN-Cat8k-Site400-OMPD-5-NTCE-400002: R0/0: OMPD: vSmart peer 10.10.10.242 state changed to Up
</span></span><span class="line"><span class="cl">*May 14 15:39:08.808: %Cisco-SDWAN-Cat8k-Site400-OMPD-6-INFO-400005: R0/0: OMPD: Number of vSmarts connected : 1
</span></span><span class="line"><span class="cl">*May 14 15:39:09.827: %Cisco-SDWAN-Cat8k-Site400-OMPD-3-ERRO-400002: R0/0: OMPD: vSmart peer 10.10.10.243 state changed to Init
</span></span><span class="line"><span class="cl">*May 14 15:39:10.584: %CRYPTO-6-ISAKMP_ON_OFF: ISAKMP is ON
</span></span><span class="line"><span class="cl">*May 14 15:39:11.756: %Cisco-SDWAN-Cat8k-Site400-OMPD-6-INFO-400002: R0/0: OMPD: vSmart peer 10.10.10.243 state changed to Handshake
</span></span><span class="line"><span class="cl">*May 14 15:39:11.762: %Cisco-SDWAN-Cat8k-Site400-OMPD-5-NTCE-400002: R0/0: OMPD: vSmart peer 10.10.10.243 state changed to Up
</span></span><span class="line"><span class="cl">*May 14 15:39:11.762: %Cisco-SDWAN-Cat8k-Site400-OMPD-6-INFO-400005: R0/0: OMPD: Number of vSmarts connected : 2
</span></span><span class="line"><span class="cl">*May 14 15:39:12.738: %Cisco-SDWAN-Cat8k-Site400-OMPD-6-INFO-400007: R0/0: OMPD: Using policy from peer 10.10.10.242
</span></span><span class="line"><span class="cl">*May 14 15:39:13.570: %Cisco-SDWAN-Cat8k-Site400-FTMD-6-INFO-1000020: R0/0: FTMD: SLA class added : class &#39;Default&#39; at index &#39;1&#39; loss = 25%, latency = 300ms, jitter = 100ms, app-probe-class = None
</span></span><span class="line"><span class="cl">*May 14 15:39:14.738: %Cisco-SDWAN-Cat8k-Site400-OMPD-6-INFO-400007: R0/0: OMPD: Using policy from peer 10.10.10.242
</span></span></code></pre></div><p>We can also see our control connections using the command <em>show sdwan control connections</em>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Cat8k-Site400# show sdwan control connections
</span></span><span class="line"><span class="cl">                                                                                       PEER                                          PEER                                          CONTROLLER
</span></span><span class="line"><span class="cl">PEER    PEER PEER            SITE       DOMAIN PEER                                    PRIV  PEER                                    PUB                                           GROUP
</span></span><span class="line"><span class="cl">TYPE    PROT SYSTEM IP       ID         ID     PRIVATE IP                              PORT  PUBLIC IP                               PORT  ORGANIZATION            LOCAL COLOR     PROXY STATE UPTIME      ID
</span></span><span class="line"><span class="cl">----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
</span></span><span class="line"><span class="cl">vsmart  dtls 10.10.10.242    100        1      192.168.99.242                          12446 192.168.99.242                          12446 SDWAN-LAB  biz-internet    No    up     0:00:00:51  0
</span></span><span class="line"><span class="cl">vsmart  dtls 10.10.10.243    100        1      192.168.99.243                          12446 192.168.99.243                          12446 SDWAN-LAB  biz-internet    No    up     0:00:00:49  0
</span></span><span class="line"><span class="cl">vmanage dtls 10.10.10.240    100        0      192.168.99.240                          12646 192.168.99.240                          12646 SDWAN-LAB  biz-internet    No    up     0:00:00:52  0
</span></span></code></pre></div><p>Of course, we can also check to see our device status in the vManage dashboard as well.</p>
<p>Over on the <strong>Monitor &gt; Network</strong> page, we can see that our new Catalyst 8000v is now online:</p>
<p><img alt="005&mdash;monitor-network" loading="lazy" src="/content/images/2021/05/005---monitor-network.PNG#center"></p>
<hr>
<h2 id="extra-how-do-i-check-the-routing-table-on-an-ios-xe-sd-wan-device">Extra: How do I check the routing table on an IOS-XE SD-WAN device?</h2>
<p>So - if you&rsquo;ve only used the vEdge software devices, you may be used to using the <strong>show ip route</strong> or <strong>show ip route vpn 10</strong> commands.</p>
<p>In the IOS-XE world, <em>most</em> SD-WAN commands are prefixed with the <strong>sdwan</strong> keyword. For example: <strong>show sdwan bfd sessions</strong> (where on vEdges, it would just be <strong>show bfd sessions</strong>)</p>
<p>This might lead you to believe that you can use <strong>show sdwan ip route</strong> or <strong>show sdwan ip route vrf 10</strong> - but these won&rsquo;t work! In fact, you&rsquo;ll get the following message:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Cat8k-Site400# show sdwan ip route
</span></span><span class="line"><span class="cl">% Error: This command is not supported
</span></span></code></pre></div><p>For the IOS-XE based devices, they actually just use the standard IOS-XE routing table and VRF constructs.</p>
<p>So on a vEdge, you would have VPN 0 as your transport VPN. On IOS-XE, this is just the default global routing table, shown with <strong>show ip route</strong>.</p>
<p>But what about our LAN-side service VPNs? In this case, our routes are being dumped into a VRF on the IOS-XE device.</p>
<p>So for example, I have VPN 10 in my lab which is used for LAN-side clients. We can use the <strong>show vrf</strong> command to see that this exists, and then <strong>show ip route vrf 10</strong> to see the routes from our other SD-WAN locations:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Cat8k-Site400# show vrf
</span></span><span class="line"><span class="cl">  Name                             Default RD            Protocols   Interfaces
</span></span><span class="line"><span class="cl">  10                               &lt;not set&gt;             ipv4        Gi2
</span></span><span class="line"><span class="cl">  512                              &lt;not set&gt;             ipv4        Gi3
</span></span><span class="line"><span class="cl">  65528                            &lt;not set&gt;             ipv4        Lo65528
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Cat8k-Site400# show ip route vrf 10
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Routing Table: 10
</span></span><span class="line"><span class="cl">Codes: L - local, C - connected, S - static, R - RIP, M - mobile, B - BGP
</span></span><span class="line"><span class="cl">       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
</span></span><span class="line"><span class="cl">       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
</span></span><span class="line"><span class="cl">       E1 - OSPF external type 1, E2 - OSPF external type 2, m - OMP
</span></span><span class="line"><span class="cl">       n - NAT, Ni - NAT inside, No - NAT outside, Nd - NAT DIA
</span></span><span class="line"><span class="cl">       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
</span></span><span class="line"><span class="cl">       ia - IS-IS inter area, * - candidate default, U - per-user static route
</span></span><span class="line"><span class="cl">       H - NHRP, G - NHRP registered, g - NHRP registration summary
</span></span><span class="line"><span class="cl">       o - ODR, P - periodic downloaded static route, l - LISP
</span></span><span class="line"><span class="cl">       a - application route
</span></span><span class="line"><span class="cl">       + - replicated route, % - next hop override, p - overrides from PfR
</span></span><span class="line"><span class="cl">       &amp; - replicated local route overrides by connected
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Gateway of last resort is not set
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      10.0.0.0/24 is subnetted, 2 subnets
</span></span><span class="line"><span class="cl">m        10.2.2.0 [251/0] via 10.10.10.235, 00:09:02, Sdwan-system-intf
</span></span><span class="line"><span class="cl">m        10.3.3.0 [251/0] via 10.10.10.236, 00:09:02, Sdwan-system-intf
</span></span></code></pre></div><hr>
<p>Okay, that&rsquo;s it! Pretty quick process overall - and now we can get into applying our device/feature templates.</p>
<p>Hope this was helpful!!</p>
]]></content:encoded>
    </item>
    <item>
      <title>[How To] Connect Cisco SD-WAN to Umbrella SIG/SWG</title>
      <link>https://0x2142.com/cisco-sdwan-and-umbrella-sig-integration/</link>
      <pubDate>Thu, 22 Apr 2021 15:17:50 +0000</pubDate>
      <guid>https://0x2142.com/cisco-sdwan-and-umbrella-sig-integration/</guid>
      <description>A tutorial for configuring Viptela SDWAN with Cisco Umbrella Secure Internet Gateway &amp;amp; Secure Web Gateway</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/kJwtIVp0-R4?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>In this post, we&rsquo;ll walk through configuring a Cisco Viptela SD-WAN network to integrate with Cisco Umbrella&rsquo;s Secure Internet Gateway (SIG) &amp; Secure Web Gateway (SWG).</p>
<p>If you&rsquo;re interested in reading more about what SIG &amp; SWG are, please check out my <a href="/meraki-mx-and-umbrella-sig-integration/">last post</a> where I walked through this integration with a Meraki MX firewall.</p>
<p>For additional reading, there&rsquo;s also a good Umbrella blog post on all the components of Cisco&rsquo;s SASE architecture, which you can find <a href="https://umbrella.cisco.com/blog/what-goes-into-the-secure-access-service-edge-sase-solution">here</a>.</p>
<p>Okay, with that being said - let&rsquo;s get started!</p>
<hr>
<h2 id="step-1-umbrella-api-keys">Step 1: Umbrella API Keys</h2>
<p>Okay, so we&rsquo;ll start on the Umbrella side. We&rsquo;ll need to generate a set of API keys, which we&rsquo;ll push out to our WAN edge devices. These API keys will allow our remote devices to reach out to Umbrella &amp; auto-configure their SIG tunnels.</p>
<p>We&rsquo;ll log into our Umbrella dashboard at <a href="https://login.umbrella.com/">https://login.umbrella.com/</a></p>
<p>Once we log in, we&rsquo;ll hop over to <strong>Admin &gt; API Keys</strong>. Then up in the upper-right corner, click <strong>Create</strong>.</p>
<p><img alt="001&mdash;umbrella-api-keys-1" loading="lazy" src="/content/images/2021/04/001---umbrella-api-keys-1.png#center"></p>
<p>For this integration, we&rsquo;ll need to select <strong>Umbrella Management</strong> to make sure our API keys have the access they need. Then click <strong>Create.</strong> Easy enough, right?</p>
<p>Once we do that, the Umbrella dashboard will display our API keys - which we&rsquo;ll need to copy over to our Viptela SD-WAN environment.</p>
<p><img alt="002&mdash;umbrella-keys" loading="lazy" src="/content/images/2021/04/002---umbrella-keys.png#center"></p>
<p>We&rsquo;ll also need to collect our Umbrella Organization ID. This is actually just embedded within the Umbrella Dashboard URL, and should be a seven-digit number. For example, if we look at our current URL on the API keys page:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">https://dashboard.umbrella.com/o/&lt;ORG ID&gt;/#/admin/apikeys
</span></span></code></pre></div><p>And that&rsquo;s all we need for now from the Umbrella side. So let&rsquo;s hop over to Viptela.</p>
<h2 id="step-2-viptela-templates--config">Step 2: Viptela Templates &amp; Config</h2>
<p>For this post, I&rsquo;ll be using my SD-WAN lab in EVE-NG. Currently I just upgraded the lab to run Viptela version 20.4.1, though the SIG integration has been supported since 20.1.</p>
<p>Just for reference, here is the lap topology that I&rsquo;m working with:</p>
<p><img alt="003&mdash;eve-topology-1" loading="lazy" src="/content/images/2021/04/003---eve-topology-1.png#center"></p>
<p>This lab includes a bridged connection to allow direct internet access from the lab network.</p>
<p>To configure our SIG automatic tunnels, we&rsquo;ll need to create / update a few templates:</p>
<ul>
<li>Create a <strong>SIG Credentials</strong> feature template</li>
<li>Create a <strong>SIG</strong> feature template</li>
<li>Assign SIG templates to device templates</li>
<li>Edit Service-side VPN Template to inject a service route</li>
</ul>
<h3 id="2a-creating-a-sig-credentials-template">2a: Creating a SIG Credentials Template</h3>
<p>First we&rsquo;ll create a template which will store our Umbrella API credentials.</p>
<p>In the vManage dashboard, we&rsquo;ll go to <strong>Configuration &gt; Templates</strong>, then drop into the <strong>Feature</strong> tab, and click on <strong>Add Template</strong>.</p>
<p><img alt="004&mdash;add-template" loading="lazy" src="/content/images/2021/04/004---add-template.png#center"></p>
<p>Once we get to the next screen, we&rsquo;ll select our device type. In my case, I&rsquo;m using a <strong>vEdge Cloud</strong>.</p>
<p>A list of templates will pop up - we&rsquo;ll drop down to <strong>SIG Credentials</strong>, which will be near the bottom under the <strong>Other Templates</strong> header.</p>
<p><img alt="005&mdash;SIGCREDTEMPLATE" loading="lazy" src="/content/images/2021/04/005---SIGCREDTEMPLATE.png#center"></p>
<p>We&rsquo;ll then be taken to the page where we create our template.</p>
<p>We&rsquo;ll fill in our template name &amp; description, then paste in our Umbrella details (Org ID, API Key, API Secret).</p>
<p><img alt="006&mdash;SIG-Cred-template-page" loading="lazy" src="/content/images/2021/04/006---SIG-Cred-template-page.png#center"></p>
<p>Once we&rsquo;re done - We&rsquo;ll hit <strong>Save</strong>.</p>
<blockquote>
<p>Note: At time of writing, the &ldquo;Get Keys&rdquo; button only functions if you&rsquo;ve purchased your SD-WAN subscription with DNA Premier licensing. This license level includes Umbrella SIG, and allows this 1-click integration that pulls your Umbrella API keys automatically.</p></blockquote>
<h3 id="2b-creating-a-sig-tunnel-template">2b: Creating a SIG Tunnel Template</h3>
<p>Okay, next we&rsquo;ll need to create another feature template to specify our IPSec tunnel configuration.</p>
<p>Just like before, we&rsquo;ll head back over to <strong>Configuration &gt; Templates &gt; Feature &gt; Add Template</strong>.</p>
<p>This time, after selecting our device type, we&rsquo;ll choose <strong>Secure Internet Gateway (SIG) / WAN</strong> - which is located under the <strong>VPN</strong> header.</p>
<p><img alt="007&mdash;SIGWANTEMPLATE" loading="lazy" src="/content/images/2021/04/007---SIGWANTEMPLATE.png#center"></p>
<p>In the template configuration, we&rsquo;ll give the template a name &amp; description as always. Then we&rsquo;ll jump down to the tunnel config.</p>
<p>We&rsquo;ll select <strong>Umbrella</strong> for SIG Provider, then click <strong>Add Tunnel</strong>.</p>
<p><img alt="008&mdash;sig-tunnel-02" loading="lazy" src="/content/images/2021/04/008---sig-tunnel-02.png#center"></p>
<p>Within the tunnel config, we&rsquo;ll specify an interface name - I&rsquo;ll name mine <em>ipsec1</em> (and I&rsquo;ll create an <em>ipsec2</em> shortly). We&rsquo;ll also specify a <em>Tunnel Source</em>, which in my lab is <strong>ge0/0</strong> for the biz-internet VPN 0 interface.</p>
<p><img alt="008&mdash;sig-tunnel-03" loading="lazy" src="/content/images/2021/04/008---sig-tunnel-03.png#center"></p>
<p>We&rsquo;ll keep <strong>Data-Center</strong> as <strong>Primary</strong>, then click <strong>Add</strong>. Then - we&rsquo;ll add a second tunnel configuration, but using <strong>Data-Center</strong> as <strong>Secondary</strong> and the interface name as <em>ipsec2</em>.</p>
<p>When all that is done, we should have the following:</p>
<p><img alt="008&mdash;sig-tunnel-04" loading="lazy" src="/content/images/2021/04/008---sig-tunnel-04.png#center"></p>
<p>If we scroll down to the bottom of the template config, we&rsquo;ll have some settings for <strong>High Availability</strong>. Here, we&rsquo;ll specify what our active &amp; backup IPSec tunnels are, and their weights. I&rsquo;ll specify <em>ipsec1</em> as active &amp; <em>ipsec2</em> as backup. Then click <strong>Save</strong> to finish our template.</p>
<blockquote>
<p>Note: Weight settings are only available starting with 20.4.1 &amp; allow for ECMP routing to SIG. Not shown above, you can create up to four HA tunnel pairs. Depending on weighting, you can equal-cost or unequal-cost load balance between those pairs.</p>
<p>Why would you want to load balance across multiple tunnels? At time of writing, each IPSec tunnel is limited to a maximum 250Mb/s throughput. By creating multiple tunnels &amp; load balancing, we can overcome this limitation if we need higher bandwidth.</p></blockquote>
<h3 id="2c-attaching-sig-to-device-templates">2c: Attaching SIG to Device Templates</h3>
<p>After we&rsquo;ve built out our two SIG templates, we can now attach them to our device templates.</p>
<p>For me, I currently only have a single device template which is applied to all of my remote WAN edge devices.</p>
<p>So we&rsquo;ll go back to <strong>Configuration &gt; Templates</strong> and find whichever device template we want to use - then click the ellipsis on the far right, end select <strong>Edit</strong>.</p>
<p><img alt="009&mdash;edit-device-template" loading="lazy" src="/content/images/2021/04/009---edit-device-template.png#center"></p>
<p>In the device template, we&rsquo;ll scroll down and look for our <strong>VPN 0</strong> configuration under <strong>Transport &amp; Management VPN</strong>. We&rsquo;ll attach our SIG tunnel template to VPN 0, since that&rsquo;s where those IPSec tunnels are being sourced from.</p>
<p>On the right side, under <strong>Additional VPN 0 Templates</strong>, we&rsquo;ll click <strong>Secure Internet Gateway</strong> to add our template. Then from the drop-down, we can select the feature template we just created:</p>
<p><img alt="010&mdash;device-template-vpn0" loading="lazy" src="/content/images/2021/04/010---device-template-vpn0.png#center"></p>
<p>We&rsquo;ll also include our SIG credentials template, which we can find at the bottom of the page, under <strong>Additional Templates</strong>:</p>
<p><img alt="011&mdash;sig-cred-template-attach-1" loading="lazy" src="/content/images/2021/04/011---sig-cred-template-attach-1.png#center"></p>
<p>Once we&rsquo;re done, we can click <strong>Save</strong> at the bottom. This will take us through the process of pushing out these changes to our remote WAN edge devices. When this configuration is deployed, each vEdge will now reach out to Umbrella &amp; auto-configure an IPSec tunnel.</p>
<blockquote>
<p>Note: While the IPSec tunnels will be established when this configuration is pushed - no traffic will flow over them yet. We&rsquo;ll need to add a service route for that, which we&rsquo;ll do next!</p></blockquote>
<h3 id="2d-injecting-a-service-route">2d: Injecting a Service Route</h3>
<p>Now we have our IPSec tunnels deployed - all we need to do is start routing traffic out to Umbrella. We&rsquo;ll accomplish this using a service route on our LAN-side VPN.</p>
<p>So we&rsquo;ll go over to <strong>Configuration &gt; Templates &gt; Feature</strong> - and we&rsquo;ll scroll down &amp; find whichever template we&rsquo;re currently using on our LAN side. For me, I want VPN 10 to route over the tunnels, so I&rsquo;ll be editing my VPN 10 template.</p>
<p>Within the template, we&rsquo;ll scroll down to the <strong>Service Route</strong> section, click <strong>New Service Route</strong>, and we&rsquo;ll enter our desired prefix. This will be what traffic will be routed over the IPSec tunnel to Umbrella. Since this is intended to be an internet gateway, I&rsquo;ll enter 0.0.0.0/0 to inject a default route to Umbrella.</p>
<p>Service should be pre-selected as <strong>SIG</strong>, so we&rsquo;ll click <strong>Add</strong>, then <strong>Update</strong> &amp; deploy our changes to the remote edge devices.</p>
<p><img alt="012&mdash;service-route" loading="lazy" src="/content/images/2021/04/012---service-route.png#center"></p>
<h2 id="step-3-validation--troubleshooting">Step 3: Validation &amp; Troubleshooting</h2>
<p>Awesome! Now we should have everything configured &amp; working. So let&rsquo;s jump through some initial testing and validation we can do.</p>
<p>Within vManage, we can check the status of our IPSec tunnels. We&rsquo;ll go over to <strong>Monitor &gt; Network</strong> then select one of our WAN edge devices.</p>
<p>We&rsquo;ll click on the <strong>Interfaces</strong> tab on the left side - and we should be able to see a list of all interfaces on our device. This should include an ipsec1 &amp; ipsec2 interface.</p>
<p>I&rsquo;ve also selected the <strong>Real Time</strong> monitor on mine, and filtered to just show traffic on the ipsec1 interface:</p>
<p><img alt="013&mdash;monitor" loading="lazy" src="/content/images/2021/04/013---monitor.png#center"></p>
<p>You might recall from my topology above, that I have a linux VM running behind each of the two vEdge Cloud appliances. Using these, I can also test web access &amp; see what my external IP is:</p>
<p><img alt="014&mdash;linuxVM" loading="lazy" src="/content/images/2021/04/014---linuxVM.png#center"></p>
<p>And sure enough, I am getting a 146.112.x.x address - which belongs to the Umbrella datacenter.</p>
<p>If we log into one of our vEdge devices, we can check the routing table with <strong>show ip route vpn 10</strong> (or whichever VPN you&rsquo;re using for the LAN-side). We should see our default 0.0.0.0/0 route via ipsec1, with a next-hop-VPN of VPN0:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">vEdge-01# show ip route vpn 10
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">     ADDRESS               PATH             PROTOCOL          NEXTHOP  NEXTHOP                         NEXTHOP
</span></span><span class="line"><span class="cl">VPN  FAMILY   PREFIX       ID    PROTOCOL   SUB TYPE  METRIC  IFNAME   ADDR     TLOC IP  COLOR  ENCAP  VPN      STATUS
</span></span><span class="line"><span class="cl">------------------------------------------------------------------------------------------------------------------------
</span></span><span class="line"><span class="cl">10   ipv4     0.0.0.0/0    0     std-ipsec  -         0       ipsec1   -        -        -      -      0        F,S
</span></span><span class="line"><span class="cl">10   ipv4     10.2.2.0/24  0     connected  -         0       ge0/2    -        -        -      -      -        F,S
</span></span></code></pre></div><p>We can also check specifically our SIG tunnel status using the command <strong>show secure-internet-gateway tunnels</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">vEdge-01# show secure-internet-gateway tunnels
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">TUNNEL                                                        API   LAST
</span></span><span class="line"><span class="cl">IF                                                            HTTP  SUCCESSFUL     TUNNEL
</span></span><span class="line"><span class="cl">NAME    TUNNEL ID  TUNNEL NAME                     FSM STATE  CODE  REQ            STATE
</span></span><span class="line"><span class="cl">-------------------------------------------------------------------------------------------
</span></span><span class="line"><span class="cl">ipsec1  530233543  SITE200SYS10x10x10x235IFipsec1  st-tun-up  200   create-tunnel  -
</span></span><span class="line"><span class="cl">ipsec2  530233542  SITE200SYS10x10x10x235IFipsec2  st-tun-up  200   create-tunnel  -
</span></span></code></pre></div><p>In that output above, we&rsquo;ll see that we have both ipsec1 &amp; ipsec2 tunnels shown. The last API queries to Umbrella have a HTTP 200, which is good. We&rsquo;ll also see our tunnel names, which we can use to find our device tunnel configuration in the Umbrella dashboard.</p>
<p>The tunnel name might look like a mess at first, but it&rsquo;s a unique identifier to represent each tunnel that was created. So if we break it down for this vEdge:</p>
<ul>
<li>This vEdge is at Site ID 200
<ul>
<li>Shown as &ldquo;SITE200&rdquo;</li>
</ul>
</li>
<li>This vEdge has a system IP of 10.10.10.235
<ul>
<li>Shown as &ldquo;SYS10x10x10x235&rdquo;</li>
</ul>
</li>
<li>This vEdge has two IPSec interfaces, ipsec1 &amp; ipsec2
<ul>
<li>Shown as &ldquo;IFipec1&rdquo; and &ldquo;IFipsec2&rdquo;</li>
</ul>
</li>
</ul>
<p>If we jump over to the Umbrella dashboard, we&rsquo;ll see the same. Within the Umbrella dashboard, we can jump to <strong>Deployments &gt; Network Tunnels</strong>.</p>
<p>On this page, we should see a total of four tunnels listed (two from each vEdge appliance):</p>
<p><img alt="015&mdash;Umbrella-tunnel-status" loading="lazy" src="/content/images/2021/04/015---Umbrella-tunnel-status.png#center"></p>
<p>And with everything configured &amp; validated - now we can move onto configuring firewall and web filtering policies within Umbrella!</p>
<p>If you&rsquo;re interested in seeing how to configure Umbrella&rsquo;s cloud firewall &amp; web filtering policies - please check out my <a href="https://www.youtube.com/watch?v=kJwtIVp0-R4">YouTube Video</a> above!</p>
]]></content:encoded>
    </item>
    <item>
      <title>[How To] Connect Meraki MX to Umbrella SIG/SWG</title>
      <link>https://0x2142.com/meraki-mx-and-umbrella-sig-integration/</link>
      <pubDate>Tue, 23 Mar 2021 16:00:00 +0000</pubDate>
      <guid>https://0x2142.com/meraki-mx-and-umbrella-sig-integration/</guid>
      <description>A tutorial for configuring a Meraki MX with Cisco Umbrella Secure Internet Gateway &amp;amp; Secure Web Gateway</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/GfVEQzxT10g?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>In today&rsquo;s blog post - we&rsquo;ll be checking out how to integrate a Meraki MX firewall to Cisco&rsquo;s Umbrella Secure Internet Gateway (SIG) &amp; Secure Web Gateway (SWG) services.</p>
<p>Interested in seeing how to do this with Cisco Viptela SD-WAN? Check out <a href="/cisco-sdwan-and-umbrella-sig-integration/">this post</a></p>
<blockquote>
<p>Note: This integration is still rather new - so I anticipate some of the screenshots &amp; steps below may differ as this post ages.</p></blockquote>
<hr>
<h2 id="what-are-sig--swg">What are SIG &amp; SWG?</h2>
<p>Good question! A lot of us are probably familiar with Cisco Umbrella (formerly OpenDNS) for the DNS-layer security products. Using Cisco Umbrella, we can configure our end PCs or DNS forwarders to use the Cisco DNS servers. Then, we apply security policies to allow/deny DNS queries based on our policy.</p>
<p>Now applying security policy at the DNS level is great! Typical web filtering only inspects HTTP/HTTPS traffic, but inspecting DNS traffic allows us to stop threats that might not use those typical ports. While there may be some applications or malware that use hard-coded IP addresses, a good majority use a domain name that requires a DNS lookup.</p>
<p>But what if we wanted to still do web filtering too? Or maybe we want a centralized, cloud-hosted firewall service? That&rsquo;s where Umbrella SIG &amp; SWG come in.</p>
<p>Secure Web Gateway (SWG) is pretty much exactly what it sounds like. It&rsquo;s a cloud-hosted web filter or web proxy, where we can set URL-based policys to permit or deny access. It supports the usual allow-lists, deny-lists, HTTPS inspection, file inspection, and policies based on user/group identities.</p>
<p>Secure Internet Gateway (SIG) is a large, cloud-hosted firewall service. What we can do with SIG is tunnel all of our traffic to one centrallized location to apply firewall policies (permit/deny/etc).</p>
<p>Why would we want either of these functions to be cloud-hosted? Well we might not have the resources at every remote site to perform firewalling &amp; web filtering - or we don&rsquo;t want to invest in beefy hardware in our corporate datacenter, and backhaul all of our remote traffic back for inspection. The cloud-hosted piece also means a central management point, so only one place to make change for all of our remote sites - rather than having to touch dozens or hundreds of remote devices&hellip;..Did someone say SASE? 🙃</p>
<p>With all that said - Let&rsquo;s get into making this work!</p>
<blockquote>
<p>Note: There are many ways to use Umbrella SIG/SWG (IPSec tunnel, PAC file, Anyconnect, etc). This particular post will only cover IPSec via Meraki MX.</p></blockquote>
<h2 id="step-1-getting-our-keys">Step 1: Getting our Keys</h2>
<p>First thing we&rsquo;ll need to do is on the Umbrella side. We&rsquo;ll need to generate tunnel keys for our Meraki MX to use for IPSec negotiation.</p>
<p>We&rsquo;ll log into our Umbrella dashboard at <a href="https://login.umbrella.com/">https://login.umbrella.com/</a></p>
<p>Once we&rsquo;re in, we&rsquo;ll navigate to <strong>Deployments</strong> &gt; <strong>Core Identities</strong> &gt; <strong>Network Tunnels</strong>. Shown below, we currently have no tunnels configured:</p>
<p><img alt="001&mdash;umbrella-keys-1" loading="lazy" src="/content/images/2021/03/001---umbrella-keys-1.PNG#center"></p>
<p>In the upper right corner, let&rsquo;s click the <strong>Add Tunnel</strong> button.</p>
<p>We&rsquo;ll now see the following screen to begin creating our IPSec tunnel:</p>
<p><img alt="002&mdash;Umbrella-tunnel-create-1" loading="lazy" src="/content/images/2021/03/002---Umbrella-tunnel-create-1.png#center"></p>
<p>Here, we&rsquo;ll need to specify a name for our tunnel &amp; which device type. The name can be anything we choose that helps us identify what locations are using this tunnel. I&rsquo;ve specified <em>MX64</em> as my tunnel name, since I only have one MX that I&rsquo;ll be testing this with. We also have <strong>Meraki MX</strong> as an option in the <em>Device Type</em> drop down - so we&rsquo;ll select that as well. Then, we&rsquo;ll click <strong>Next</strong>.</p>
<p>Then we&rsquo;ll need to configure our <strong>Tunnel ID</strong> and <strong>Passphrase</strong>:</p>
<p><img alt="003&mdash;Umbrella-tunnel-create-2" loading="lazy" src="/content/images/2021/03/003---Umbrella-tunnel-create-2.png#center"></p>
<p>The <strong>Tunnel ID</strong> we&rsquo;ll be sending later, as part of our IPSec local ID. The passphrase will be used as our pre-shared key for the tunnel config. Once we&rsquo;re done with that, click <strong>Save</strong>.</p>
<p>As long as our ID &amp; passphrase are all good, we&rsquo;ll be presented with a box to easily copy these parameters into the Meraki config:</p>
<p><img alt="004&mdash;Umbrella-tunnel-create-3" loading="lazy" src="/content/images/2021/03/004---Umbrella-tunnel-create-3.png#center"></p>
<p>Finally, we&rsquo;ll be dropped back to our <em>Network Tunnels</em> page - where we can see that our tunnel is configured, but not yet established. Let&rsquo;s fix that &amp; hop over to the Meraki dashboard!</p>
<p><img alt="005&mdash;Umbrella-tunnel-create-4" loading="lazy" src="/content/images/2021/03/005---Umbrella-tunnel-create-4.png#center"></p>
<h2 id="step-2-meraki-mx-ipsec-configuration">Step 2: Meraki MX IPSec Configuration</h2>
<p>Okay, now onto the fun stuff.</p>
<p>We&rsquo;ll log into our Meraki Dashboard at <a href="https://account.meraki.com/secure/login/dashboard_login">https://account.meraki.com/secure/login/dashboard_login</a></p>
<p>Once we&rsquo;re in, you&rsquo;ll need to select the network with the MX you want to connect. For me, I currently only have a single network, which contains my MX firewall.</p>
<p>Then we&rsquo;ll navigate to <strong>Security &amp; SD-WAN</strong> &gt; <strong>Configure</strong> &gt; <strong>Site-to-site VPN</strong>.</p>
<p><img alt="006&mdash;Meraki-MX-VPN-1" loading="lazy" src="/content/images/2021/03/006---Meraki-MX-VPN-1.png#center"></p>
<p>Assuming we have no other VPNs configured, you&rsquo;ll have to change the VPN <strong>Type</strong> to <strong>Hub (Mesh)</strong> or <strong>Spoke</strong>. In my case, Umbrella SIG will be the only VPN I&rsquo;ll have configured - so I&rsquo;ll go ahead and use <strong>Hub (Mesh)</strong>. Don&rsquo;t mind the error regarding exit hubs, as we&rsquo;ll be configuring a non-Meraki peer below.</p>
<p><img alt="007&mdash;Meraki-MX-VPN-2" loading="lazy" src="/content/images/2021/03/007---Meraki-MX-VPN-2.PNG#center"></p>
<p>Okay, scrolling down the page just a bit - we&rsquo;ll need to specify which VLANs/internal subnets will be routed across the VPN &amp; pushed through Umbrella for SIG/SWG:</p>
<p><img alt="008&mdash;Meraki-MX-VPN-3-1" loading="lazy" src="/content/images/2021/03/008---Meraki-MX-VPN-3-1.PNG#center"></p>
<p>For testing purposes, I have a VM in a subnet labeled <em>DMZ</em> that I&rsquo;ll be using - So that will be the only VLAN that I&rsquo;ll set to <strong>VPN On</strong>.</p>
<p>Next we&rsquo;ll take a look at configuring <strong>Non-Meraki VPN Peers</strong>.</p>
<p>As shown in the screenshot below, we&rsquo;ll need to configure the following settings:</p>
<ul>
<li>Name - This is locally significant, I set mine to <em>Umbrella_SIG</em></li>
<li>IKE Version - Set this to IKEv2</li>
<li>IPSec Policies - Custom - See below</li>
<li>Public IP - This is the peer Umbrella Datacenter
<ul>
<li>Choose the DC closest to you <a href="https://docs.umbrella.com/umbrella-user-guide/docs/cisco-umbrella-data-centers">here</a></li>
<li>For this post, I&rsquo;m using the New York DC</li>
</ul>
</li>
<li>Local ID - Set this to the Tunnel ID we got from Umbrella</li>
<li>Remote ID - Leave blank</li>
<li>Private Subnets - This is what destination IPs will be routed over the tunnel
<ul>
<li>Since this is intended as an <em>Internet</em> gateway, we&rsquo;ll use 0.0.0.0/0</li>
</ul>
</li>
<li>Preshared Secret - Set this to the passphrase we configured in Umbrella</li>
<li>Availability - Set this to which networks this tunnel should apply
<ul>
<li>Since I only have 1 network &amp; 1 MX, I set this to <em>All Networks</em></li>
</ul>
</li>
</ul>
<p><img alt="009&mdash;Meraki-MX-VPN-4" loading="lazy" src="/content/images/2021/03/009---Meraki-MX-VPN-4.png#center"></p>
<p>Okay - I mentioned above we&rsquo;ll need to set some custom IPSec parameters. Here&rsquo;s what we&rsquo;ll configure as a custom policy:</p>
<p><img alt="010&mdash;Meraki-MX-VPN-5" loading="lazy" src="/content/images/2021/03/010---Meraki-MX-VPN-5.PNG#center"></p>
<blockquote>
<p>Note: Turns out in the drop down menu, there is also an option for &ldquo;Umbrella&rdquo;, so you don&rsquo;t have to create a custom policy. That being said, the Umbrella preset uses DH group 5, but Umbrella&rsquo;s docs ask for DH group 14.</p></blockquote>
<p>Lastly, we&rsquo;ll be able to configure whether or not we want firewall logging (I enabled this) and also whether we want to add access-lists to permit/deny any traffic over the VPN. For now I&rsquo;ll be leaving this as permit any.</p>
<p><img alt="011&mdash;Meraki-MX-6" loading="lazy" src="/content/images/2021/03/011---Meraki-MX-6.PNG#center"></p>
<p>Finally - we can click <strong>Save</strong> to push our configuration to the MX appliance!</p>
<h2 id="step-3-validation--testing">Step 3: Validation &amp; Testing</h2>
<p>Hopefully once we get all that configuration done, we&rsquo;ll be able to see our tunnel come up.</p>
<p>We can validate from both sides, but let&rsquo;s start with Meraki. In the Dashboard menu, we&rsquo;ll navigate to <strong>Security &amp; SD-WAN</strong> &gt; <strong>Monitor</strong> &gt; <strong>VPN Status</strong>.</p>
<p><img alt="012&mdash;Meraki-MX-VPN-7" loading="lazy" src="/content/images/2021/03/012---Meraki-MX-VPN-7.png#center"></p>
<p>Then we&rsquo;ll need to click the tab for <strong>Non-Meraki peer</strong>:</p>
<p>With any luck, we should see the little green circle showing that our tunnel is online.</p>
<p><img alt="016&mdash;MX-VPN-Status" loading="lazy" src="/content/images/2021/03/016---MX-VPN-Status.PNG#center"></p>
<blockquote>
<p>If your tunnel doesn&rsquo;t come up immediately, you may have to generate some traffic from your VPN subnet to the internet. This will force the MX to try and connect to Umbrella.</p>
<p>It&rsquo;s also worth noting - sometimes it may take the Meraki dashboard to show a successful connection. A better indicator is by checking the Umbrella dashboard, or checking manually with a device you are expecting to route over the VPN.</p></blockquote>
<p>If for any reason we have trouble, we can also check our VPN negotiation logs by going to <strong>Network-Wide</strong> &gt; <strong>Monitor</strong> &gt; <strong>Event Log</strong>.</p>
<p><img alt="014&mdash;Meraki-MX-VPN-9" loading="lazy" src="/content/images/2021/03/014---Meraki-MX-VPN-9.png#center"></p>
<p>On this screen, make sure you&rsquo;ve selected <strong>for security appliances</strong> at the top. If needed, we can also filter events by <strong>All Non-Meraki VPN / Client VPN</strong>.</p>
<p><img alt="015&mdash;Meraki-MX-VPN-10" loading="lazy" src="/content/images/2021/03/015---Meraki-MX-VPN-10.png#center"></p>
<p>Since my tunnel came up with no issues, the output above shows a successful tunnel negotiation.</p>
<p>Let&rsquo;s jump over to the Umbrella side, and see what the Umbrella dashboard shows.</p>
<p>Back on the <strong>Network Tunnels</strong> page - we should see that our tunnel now shows as <strong>Active</strong>.</p>
<p><img alt="013&mdash;Meraki-MX-VPN-8" loading="lazy" src="/content/images/2021/03/013---Meraki-MX-VPN-8.png#center"></p>
<p>This screen will also so the public IP that our MX is using to connect, as well as which Umbrella datacenter we&rsquo;ve connected to.</p>
<hr>
<p>Okay! That&rsquo;s it for now. To try and keep this blog post from getting too long, I&rsquo;ve decided to split this into two parts.</p>
<p>If you&rsquo;re interested in seeing how to configure Umbrella&rsquo;s cloud firewall &amp; web filtering policies - please check out my <a href="https://www.youtube.com/watch?v=GfVEQzxT10g">YouTube Video</a> above!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Getting Started with IOS-XE Guestshell</title>
      <link>https://0x2142.com/getting-started-with-ios-xe-guestshell/</link>
      <pubDate>Tue, 26 Jan 2021 16:15:41 +0000</pubDate>
      <guid>https://0x2142.com/getting-started-with-ios-xe-guestshell/</guid>
      <description>In this post, we&amp;rsquo;ll explore how to set up Cisco IOX &amp;amp; Guestshell for on-box Python</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/KiVbDq0Czdg?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>This post has been on my backlog for a while&hellip; I&rsquo;ve been intermittently trying out the IOS-XE guestshell feature for a few different projects, and never feeling like any of them were good enough to write about.</p>
<p>So why not just write about guestshell itself? It&rsquo;s such a nifty tool.</p>
<hr>
<h2 id="what-is-guestshell">What is Guestshell?</h2>
<p>Now that Cisco&rsquo;s Catalyst platform has standardized on IOS-XE, we can take advantage of some of the features of the Linux-based platform. One of which being Linux Containers (LXC).</p>
<p>Most of the latest Catalyst routers, switches, and access points support what Cisco calls Application Hosting - which is a fancy way of just saying it can run containers on-box. Another acronym to be aware of is IOx - which is what Cisco calls their IOS-XE/Linux appliction framework.</p>
<p>Guestshell is a specific feature within IOS-XE, where a dedicated pre-built container is enabled for on-box access to a Linux shell.</p>
<h2 id="what-hardware-supports-guestshell">What Hardware Supports Guestshell?</h2>
<p>Okay - so for this example, I&rsquo;ll be using the new Catalyst 8000V platform (a virtual Catalyst router). I do also have a Catalyst 9200L on hand, but unfortunately it&rsquo;s one of the few Catalyst platforms that <em>does not</em> support Guestshell (due to lower-end HW specs).</p>
<p>You can also use the Catalyst 8000V to test this in a lab, or you could use most of the Catalyst 8000 / 9000 series platforms if you had them available (or the CSRv &amp; some ISR routers!).</p>
<p>You can find a list of supported platforms &amp; other hardware requirements <a href="https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/prog/configuration/166/b_166_programmability_cg/guest_shell.html#id_45838">here</a>.</p>
<p>All that being said - let&rsquo;s get started!</p>
<h2 id="enabling-iox">Enabling IOx</h2>
<p>So before we can actually use the guestshell feature, there is some pre-configuration to be done.</p>
<p>First we&rsquo;ll need to ensure that the IOx application framework is enabled &amp; running. We can check this by using the <strong>show iox</strong> command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0x8KV01# show iox
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">IOx Infrastructure Summary:
</span></span><span class="line"><span class="cl">---------------------------
</span></span><span class="line"><span class="cl">IOx service (CAF)              : Not Running
</span></span><span class="line"><span class="cl">IOx service (HA)               : Not Supported
</span></span><span class="line"><span class="cl">IOx service (IOxman)           : Not Running
</span></span><span class="line"><span class="cl">IOx service (Sec storage)      : Not Supported
</span></span><span class="line"><span class="cl">Libvirtd 5.5.0                 : Running
</span></span></code></pre></div><p>And as we see above - most components are listed as &ldquo;Not Running&rdquo; or &ldquo;Not Supported&rdquo;.</p>
<p>So we&rsquo;ll enter config mode, and use the <em>iox</em> command to enable these services:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0x8KV01# conf t
</span></span><span class="line"><span class="cl">Enter configuration commands, one per line.  End with CNTL/Z.
</span></span><span class="line"><span class="cl">0x8KV01(config)# iox 
</span></span><span class="line"><span class="cl">0x8KV01(config)#
</span></span><span class="line"><span class="cl">0x8KV01(config)# exit
</span></span><span class="line"><span class="cl">0x8KV01# show iox
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">IOx Infrastructure Summary:
</span></span><span class="line"><span class="cl">---------------------------
</span></span><span class="line"><span class="cl">IOx service (CAF)              : Running
</span></span><span class="line"><span class="cl">IOx service (HA)               : Not Supported
</span></span><span class="line"><span class="cl">IOx service (IOxman)           : Running
</span></span><span class="line"><span class="cl">IOx service (Sec storage)      : Not Supported
</span></span><span class="line"><span class="cl">Libvirtd 5.5.0                 : Running
</span></span></code></pre></div><h2 id="configuring-guestshell-networking">Configuring Guestshell Networking</h2>
<p>There&rsquo;s a good chance we&rsquo;ll want our container to have access to the outside world, so let&rsquo;s take a quick look at the network configuration.</p>
<p><strong>Note: The networking config syntax will differ if you are using a Catalyst router or a Catalyst switch. Since I am using a Catalyst 8000V - we will look at the config for a Catalyst router first.</strong></p>
<p>In order to allow external access to our container, we&rsquo;ll need to configure a gateway interface and NAT translations. Then we can provide a full network configuration to our container:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">## Enable NAT on our external interface - in my case, gigabitEthernet1
</span></span><span class="line"><span class="cl">0x8KV01(config)# interface gigabitEthernet 1
</span></span><span class="line"><span class="cl">0x8KV01(config-if)# ip nat outside
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">## Create a virtual interface for the guestshell container
</span></span><span class="line"><span class="cl">0x8KV01(config-if)# int virtualportgroup0
</span></span><span class="line"><span class="cl">0x8KV01(config-if)# ip address 192.168.100.1 255.255.255.0
</span></span><span class="line"><span class="cl">0x8KV01(config-if)# ip nat inside
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">## Create an ACL to match traffic from guestshell &amp; Enable NAT
</span></span><span class="line"><span class="cl">0x8KV01(config)# ip access-list standard IOX_NAT
</span></span><span class="line"><span class="cl">0x8KV01(config-std-nacl)# permit 192.168.100.0 0.0.0.255
</span></span><span class="line"><span class="cl">0x8KV01(config)# ip nat inside source list IOX_NAT interface gigabitEthernet 1 overload
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">## Assign network configuration to our guestshell container
</span></span><span class="line"><span class="cl">0x8KV01(config)#app-hosting appid guestshell
</span></span><span class="line"><span class="cl">0x8KV01(config-app-hosting)# app-vnic gateway0 virtualportgroup 0 guest-interface 0
</span></span><span class="line"><span class="cl">0x8KV01(config-app-hosting-gateway0)# guest-ipaddress 192.168.100.5 netmask 255.255.255.0
</span></span><span class="line"><span class="cl">0x8KV01(config-app-hosting-gateway0)# app-default-gateway 192.168.100.1 guest-interface 0
</span></span><span class="line"><span class="cl">0x8KV01(config-app-hosting)# name-server0 8.8.8.8
</span></span></code></pre></div><p>Now, as I mentioned before - this config will look different if you&rsquo;re using a switch vs a router.</p>
<p>So let&rsquo;s take a quick look at what this would look like, if we were on a Catalyst Switch instead:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Cat9k(config)# interface AppGigabitEthernet 1/0/1
</span></span><span class="line"><span class="cl">Cat9k(config-if)# switchport mode trunk
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Cat9k(config)# app-hosting appid name
</span></span><span class="line"><span class="cl">Cat9k(config-app-hosting)# app-vnic AppGigabitEthernet trunk
</span></span><span class="line"><span class="cl">Cat9k(config-app-hosting-trunk)# vlan 10 guest-interface 0
</span></span><span class="line"><span class="cl">Cat9k(config-app-hosting-vlan-access-ip))# guest-ipaddress 192.168.100.1 netmask 255.255.255.0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Cat9k(config-app-hosting)# app-default-gateway 192.168.100.1 guest-interface 0
</span></span><span class="line"><span class="cl">Cat9k(config)# name-server 8.8.8.8 
</span></span></code></pre></div><p>Once all that config is done - Let&rsquo;s move on to getting our container started!!</p>
<h2 id="managing-the-guestshell-process">Managing the Guestshell Process</h2>
<p>In order to start a container on our Catalyst device, we&rsquo;ll need to go through a process of deploying the app, activating it, then starting it.</p>
<p>For guestshell, there is a shortcut command that will perform all of these steps for you - and make managing the guestshell process much easier.</p>
<p>So let&rsquo;s go ahead and spin up our guestshell container with the command <strong>guestshell enable</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0x8KV01# guestshell enable
</span></span><span class="line"><span class="cl">Interface will be selected if configured in app-hosting
</span></span><span class="line"><span class="cl">Please wait for completion
</span></span><span class="line"><span class="cl">guestshell installed successfully
</span></span><span class="line"><span class="cl">Current state is: DEPLOYED
</span></span><span class="line"><span class="cl">guestshell activated successfully
</span></span><span class="line"><span class="cl">Current state is: ACTIVATED
</span></span><span class="line"><span class="cl">guestshell started successfully
</span></span><span class="line"><span class="cl">Current state is: RUNNING
</span></span><span class="line"><span class="cl">Guestshell enabled successfully
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">0x8KV01# show app-hosting list
</span></span><span class="line"><span class="cl">App id                                   State
</span></span><span class="line"><span class="cl">---------------------------------------------------------
</span></span><span class="line"><span class="cl">guestshell                               RUNNING
</span></span></code></pre></div><p>And as you can see above, everything gets automatically deployed &amp; started. We can verify using the <strong>show app-hosting list</strong> command to see that our container has been started.</p>
<p>If we need to, we can also stop &amp; deactivate our container using the command <strong>guestshell disable</strong>.</p>
<p>We could also use the commands below if we wanted to perform these steps manually:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0x8KV01# app-hosting activate appid guestshell
</span></span><span class="line"><span class="cl">0x8KV01# app-hosting start appid guestshell
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">0x8KV01# app-hosting stop appid guestshell
</span></span><span class="line"><span class="cl">0x8KV01# app-hosting deactivate appid guestshell
</span></span></code></pre></div><p>Before we move onto accessing the guestshell interface, I also wanted to show where you can get more detail about the guestshell container.</p>
<p>Using the <strong>show app-hosting detail appid guestshell</strong> command, we can see details around the container version, current MAC/IP config, and resource consumption:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0x8KV01# show app-hosting detail appid guestshell
</span></span><span class="line"><span class="cl">App id                 : guestshell
</span></span><span class="line"><span class="cl">Owner                  : iox
</span></span><span class="line"><span class="cl">State                  : RUNNING
</span></span><span class="line"><span class="cl">Application
</span></span><span class="line"><span class="cl">  Type                 : lxc
</span></span><span class="line"><span class="cl">  Name                 : GuestShell
</span></span><span class="line"><span class="cl">  Version              : 3.2.0
</span></span><span class="line"><span class="cl">  Description          : Cisco Systems Guest Shell XE for x86_64
</span></span><span class="line"><span class="cl">  Path                 : /guestshell/:guestshell.tar
</span></span><span class="line"><span class="cl">  URL Path             :
</span></span><span class="line"><span class="cl">Activated profile name : custom
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Resource reservation
</span></span><span class="line"><span class="cl">  Memory               : 256 MB
</span></span><span class="line"><span class="cl">  Disk                 : 1 MB
</span></span><span class="line"><span class="cl">  CPU                  : 800 units
</span></span><span class="line"><span class="cl">  CPU-percent          : 3 %
</span></span><span class="line"><span class="cl">  VCPU                 : 1
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Attached devices
</span></span><span class="line"><span class="cl">  Type              Name               Alias
</span></span><span class="line"><span class="cl">  ---------------------------------------------
</span></span><span class="line"><span class="cl">  serial/shell     iox_console_shell   serial0
</span></span><span class="line"><span class="cl">  serial/aux       iox_console_aux     serial1
</span></span><span class="line"><span class="cl">  serial/syslog    iox_syslog          serial2
</span></span><span class="line"><span class="cl">  serial/trace     iox_trace           serial3
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Network interfaces
</span></span><span class="line"><span class="cl">   ---------------------------------------
</span></span><span class="line"><span class="cl">eth0:
</span></span><span class="line"><span class="cl">   MAC address         : 52:54:dd:d:bf:da
</span></span><span class="line"><span class="cl">   IPv4 address        : 192.168.100.5
</span></span><span class="line"><span class="cl">   IPv6 address        : ::
</span></span><span class="line"><span class="cl">   Network name        : VPG0
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Port forwarding
</span></span><span class="line"><span class="cl">  Table-entry  Service  Source-port  Destination-port
</span></span><span class="line"><span class="cl">  ---------------------------------------------------
</span></span></code></pre></div><h2 id="accessing-the-guestshell-cli">Accessing the Guestshell CLI</h2>
<p>Now for the fun part! Let&rsquo;s get into our guestshell container.</p>
<p>We&rsquo;ll just need to use the <strong>guestshell</strong> command, and we&rsquo;ll be dropped into the Linux shell:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0x8KV01# guestshell
</span></span><span class="line"><span class="cl">[guestshell@guestshell ~]$
</span></span><span class="line"><span class="cl">[guestshell@guestshell ~]$ uname -a
</span></span><span class="line"><span class="cl">Linux guestshell 5.4.40 #1 SMP Tue Oct 6 17:57:00 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
</span></span></code></pre></div><p>Let&rsquo;s also make sure our network &amp; NAT configuration worked:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[guestshell@guestshell ~]$ ping 8.8.8.8
</span></span><span class="line"><span class="cl">PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
</span></span><span class="line"><span class="cl">64 bytes from 8.8.8.8: icmp_seq=1 ttl=64 time=3.57 ms
</span></span><span class="line"><span class="cl">64 bytes from 8.8.8.8: icmp_seq=2 ttl=64 time=2.64 ms
</span></span><span class="line"><span class="cl">64 bytes from 8.8.8.8: icmp_seq=3 ttl=64 time=2.54 ms
</span></span><span class="line"><span class="cl">^C
</span></span><span class="line"><span class="cl">--- 8.8.8.8 ping statistics ---
</span></span><span class="line"><span class="cl">3 packets transmitted, 3 received, 0% packet loss, time 4ms
</span></span><span class="line"><span class="cl">rtt min/avg/max/mdev = 2.543/2.916/3.567/0.465 ms
</span></span></code></pre></div><p>If needed, we can also run commands on our Catalyst device CLI without leaving the guestshell interface by using the <strong>dohost</strong> utility:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[guestshell@guestshell ~]$ dohost &#34;show app-hosting list&#34;
</span></span><span class="line"><span class="cl">App id                                   State
</span></span><span class="line"><span class="cl">---------------------------------------------------------
</span></span><span class="line"><span class="cl">guestshell                               RUNNING
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[guestshell@guestshell ~]$
</span></span></code></pre></div><p>Just a note, the reverse is also possible. Using the <strong>guestshell run</strong> command from our Catalyst CLI, we can run commands in our container:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0x8KV01# guestshell run uname -a
</span></span><span class="line"><span class="cl">Linux guestshell 5.4.40 #1 SMP Tue Oct 6 17:57:00 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">0x8KV01#
</span></span></code></pre></div><p>It&rsquo;s also worth reiterating, that the guestshell container is a full Linux container (It runs CentOS). So if we need to install additional Linux packages, we can use our Linux package manager (YUM) to do so.</p>
<p>Anyways - Let&rsquo;s wrap this up by looking at the built-in Python modules &amp; capabilities</p>
<h2 id="using-python-in-guestshell">Using Python in Guestshell</h2>
<p>One of the more appealing use cases for guestshell is the ability to run native Python scripts &amp; code directly on your Catalyst device.</p>
<p>A couple of possible use cases could be:</p>
<ul>
<li>Troubleshoot intermittent issues - Automatically collect device state (routes, MAC changes, etc) on a regular basis to log locally or send via email</li>
<li>Automated resolution - If a certain type of state change is observed, we could run a pre-set list of commands to try and automatically fix the issue</li>
<li>Local monitoring / troubleshooting - We could run a series of ping, web page load tests, etc from a python script to help monitor performance from the switch&rsquo;s perspective</li>
<li>Change management / notification - A script could be written to monitor the device configuration for any changes made, then send an automated email when something was detected</li>
<li>Have a risky change that might sever your connection to a remote device? A script could execute the change for you, and if unsuccessful, revert to a working configuration (without doing a <em>reload after</em>)</li>
</ul>
<p>These are just a handful of ideas that came to mind while writing this blog post&hellip; But there are many more possibilities!</p>
<p>Within the guestshell container, there is a special <strong>cli</strong> python module that comes pre-loaded. This module allows us to run CLI commands on our catalyst device directly from a python script &amp; receive the command output for parsing.</p>
<p>Let&rsquo;s look at a quick example using the Python interactive interpreter to save the device configuration:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">[guestshell@guestshell ~]$ python3
</span></span><span class="line"><span class="cl">Python 3.6.8 (default, Aug 24 2020, 17:57:11)
</span></span><span class="line"><span class="cl">[GCC 8.3.1 20191121 (Red Hat 8.3.1-5)] on linux
</span></span><span class="line"><span class="cl">Type &#34;help&#34;, &#34;copyright&#34;, &#34;credits&#34; or &#34;license&#34; for more information.
</span></span><span class="line"><span class="cl">&gt;&gt;&gt; 
</span></span><span class="line"><span class="cl">&gt;&gt;&gt; import cli
</span></span><span class="line"><span class="cl">&gt;&gt;&gt; status = cli.execute(&#34;write memory&#34;)
</span></span><span class="line"><span class="cl">&gt;&gt;&gt; if &#34;OK&#34; in status: print(&#34;Device configuration was saved!&#34;)
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">Device configuration was saved!
</span></span></code></pre></div><p>The example above uses the <strong>cli.execute</strong> Python function, which will run commands in Cisco&rsquo;s exec mode.</p>
<p>What if we wanted to make configuration changes? In that case we would use the <strong>cli.configure</strong> function:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&gt;&gt;&gt; config_data = &#34;&#34;&#34;interface loopback200
</span></span><span class="line"><span class="cl">... ip address 172.16.99.10 255.255.255.0
</span></span><span class="line"><span class="cl">... &#34;&#34;&#34;
</span></span><span class="line"><span class="cl">&gt;&gt;&gt;
</span></span><span class="line"><span class="cl">&gt;&gt;&gt; cli.configure(config_data)
</span></span><span class="line"><span class="cl">&#39;Line 1 SUCCESS:  interface loopback200\nLine 2 SUCCESS:  ip address 172.16.99.10 255.255.255.0\n&#39;
</span></span><span class="line"><span class="cl">&gt;&gt;&gt;
</span></span><span class="line"><span class="cl">&gt;&gt;&gt; cli.execute(&#34;show run interface loopback200&#34;)
</span></span><span class="line"><span class="cl">&#39;Building configuration...\nCurrent configuration : 68 bytes\n!\ninterface Loopback200\n ip address 172.16.99.10 255.255.255.0\nend\n&#39;
</span></span></code></pre></div><p>In the above example, we used a multi-line comment to provide the list of commands to enter. The <strong>cli.configure</strong> function also supports a Python list - so we could provide the commands in that format as well.</p>
<p>You&rsquo;ll notice that the Python interpreter returns a status for each command that we attempted to run. This should allow us to error-check &amp; ensure that all of our intended configuration was accepted.</p>
<p>Lastly, we used the <strong>cli.execute</strong> command again to validate that our running configuration looks correct.</p>
<p>There&rsquo;s a bit more functionality built into the <strong>cli</strong> Python module, but I just wanted to cover some of the common use cases. More details can be found <a href="https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/prog/configuration/1610/b_1610_programmability_cg/python_api.html">here</a></p>
<hr>
<h2 id="a-few-common-questions">A Few Common Questions</h2>
<p><strong>Does this give me access to the underlying IOS-XE Linux shell?</strong></p>
<p>Nope, anything running in guestshell is running in a container - which runs <em>on-top</em> of the IOS-XE image.</p>
<p><strong>What about resource conflicts?</strong></p>
<p>The linux containers running on the IOS-XE platform are given their own dedicated resources. Any resource constraints that affect the containers will not impact normal network functions.</p>
]]></content:encoded>
    </item>
    <item>
      <title>[How To] Upgrade a Cisco SD-WAN Network</title>
      <link>https://0x2142.com/how-to-upgrade-a-cisco-sd-wan-network/</link>
      <pubDate>Fri, 27 Nov 2020 20:55:48 +0000</pubDate>
      <guid>https://0x2142.com/how-to-upgrade-a-cisco-sd-wan-network/</guid>
      <description>A short tutorial on how to upgrade a Cisco/Viptela SDWAN network, including controllers &amp;amp; edge devices</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/BuRQ0P1tq4Y?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>I have a local Cisco SD-WAN lab environment running at home, which was built in EVE-NG. It&rsquo;s what I use whenever I need to test something for a customer, or just play around with the templates or APIs.</p>
<p>I&rsquo;m planning on spending some hands-on time with new features soon, along with working on some automation projects - so it&rsquo;s well past time to upgrade my lab.</p>
<p>Currently I&rsquo;m running on version 18.4.302, but my intent is to upgrade to 20.3.2 - which is the latest version available today.</p>
<p>In this blog post, we&rsquo;ll walk through how to upgrade a Cisco SD-WAN / Viptela network, including:</p>
<ul>
<li>Control Plane:
<ul>
<li>vManage</li>
<li>vBond</li>
<li>vSmart(s)</li>
</ul>
</li>
<li>Data Plane:
<ul>
<li>vEdge / vEdge Cloud</li>
</ul>
</li>
</ul>
<p>While my lab is using the on-prem controllers, these steps will work just the same if you&rsquo;re using Cisco&rsquo;s cloud-hosted controllers.</p>
<hr>
<h2 id="downloading-the-software-images">Downloading the Software Images</h2>
<p>First thing&rsquo;s first - in order to upgrade our environment, we need the correct software images!</p>
<p>Head over to Cisco Software Downloads, and search for SD-WAN (or <a href="https://software.cisco.com/download/home/286320995/type">click here!</a>).</p>
<p><img alt="03&mdash;software-download-pt2&mdash;cropped" loading="lazy" src="/content/images/2020/11/03---software-download-pt2---cropped.png#center"></p>
<p>Once on this page, I just want to call out that we will need to click on the <strong>SD-WAN Software Update</strong> link. This may seem simple enough, but the other links for <strong>vManage Software</strong> or <strong>vSmart Software</strong> only contain images for a <em>new</em> install.</p>
<p>After that - just select the image version that you would like to upgrade to, and download both images.</p>
<p>In my case, since I&rsquo;m downloading version 20.3.2, I&rsquo;ll be using the following images:</p>
<ul>
<li>vSmart, vEdge Cloud, vEdge 5000, ISR1100 series and vBond upgrade image
<ul>
<li>File name: viptela-20.3.2-x86_64.tar.gz</li>
</ul>
</li>
<li>vManage upgrade image
<ul>
<li>File name: vmanage-20.3.2-x86_64.tar.gz</li>
</ul>
</li>
</ul>
<h2 id="adding-images-to-the-software-repository">Adding Images to the Software Repository</h2>
<p>The process for upgrading a Cisco SD-WAN environment is pretty straightforward.</p>
<p>The control plane can be upgraded independently of the edge devices, so long as everything stays within the bounds of the <a href="https://www.cisco.com/c/en/us/td/docs/routers/sdwan/release/notes/vedge-20-3/sd-wan-rel-notes-20-3.html#concept_iv2_pyz_blb">compatability matrix</a>. In my case, I&rsquo;ll be upgrading to 20.3.2 - and the controllers could still support edge devices as far back as 17.2. So no issues here!</p>
<p>Alright - let&rsquo;s get started!</p>
<p>First, we&rsquo;ll need to upload our images into our local vManage software repository. This is the file storage for all upgrade images, and it&rsquo;s where the controllers &amp; edge devices will go to pull their images from.</p>
<p>In the vManage dashboard, we&rsquo;ll go to the <strong>Maintenance</strong> tab and select <strong>Software Repository</strong>.</p>
<p>Then, click on <strong>Add New Software</strong>.</p>
<p><img alt="06&mdash;vmanage-software-repo&mdash;cropped" loading="lazy" src="/content/images/2020/11/06---vmanage-software-repo---cropped.png#center"></p>
<p>In here we&rsquo;ll see a few options: <strong>vManage</strong> and <strong>Remote Server / Remote Server - vManage</strong>.</p>
<p>We&rsquo;ll use vManage if we want to upload &amp; distribute images from the local vManage server we&rsquo;re logged into currently.</p>
<p>Alternatively, we could use a remote file storage server by using the <strong>Remote Server</strong> option. If you choose to go this route, don&rsquo;t forget to ensure that ALL controllers &amp; WAN edge devices have access to this storage location.</p>
<p>After you select an option, it&rsquo;s an easy drag &amp; drop to upload the software images.</p>
<h2 id="planning-the-upgrade">Planning the Upgrade</h2>
<p>So before we get into actually applying our image upgrades - let&rsquo;s address the questions of &ldquo;What order do I upgrade things in?&rdquo; and &ldquo;What&rsquo;s the impact?&rdquo;.</p>
<p>Since this is a lab environment for me, I&rsquo;ll be upgrading everything all at once - since uptime / outages aren&rsquo;t a factor here</p>
<p>If you&rsquo;re doing this in a production environment, I highly recommend performing these upgrades in an outage / maintenance window - or at least an off-peak time.</p>
<p>Yes, you can upgrade the controllers at any time without causing any issue. Yes, you can upgrade a redundant pair of vEdge devices and keep a branch online. However, I would advise you to try these out off-hours first - and get your own understanding of how this works &amp; what to expect before doing it in production.</p>
<p>As for the upgrade order, we&rsquo;re going to start at the top of the food chain and work our way down:</p>
<ul>
<li>Upgrade vManage first</li>
<li>Then vBond</li>
<li>Upgrade ONE vSmart controller &amp; wait for it to come online / re-establish control connections</li>
<li>Then upgrade the second / redundant vSmart controller</li>
<li>After the control plane is upgraded &amp; stable - move onto the edge devices</li>
</ul>
<p>These steps can also be found under the <strong>Best Practices</strong> section of the <strong><a href="https://www.cisco.com/c/en/us/td/docs/routers/sdwan/configuration/sdwan-xe-gs-book/hardware-and-software-installation.html#c_Software_Installation_and_Upgrade_for_vEdge_Routers_1369.xml">Cisco SD-WAN Getting Started Guide</a></strong>. Cisco&rsquo;s official recommendation is to wait 24 hours in between a few of those steps, to ensure platform stability - but for my lab that won&rsquo;t be necessary</p>
<h2 id="upgrading-vmanage">Upgrading vManage</h2>
<p>Once we have uploaded our images &amp; we have our upgrade plan - we can move forward with actually performing the image upgrades.</p>
<p>Starting with vManage - we&rsquo;ll go to <strong>Maintenance</strong> &gt; <strong>Software Upgrades</strong> &gt; <strong>vManage</strong>.</p>
<p>Then we&rsquo;ll click on <strong>Upgrade</strong> and select our version - in my case <strong>20.3.2</strong>. After that, just click <strong>Upgrade</strong></p>
<p><img alt="vmange-upgrade-dialog" loading="lazy" src="/content/images/2020/11/vmange-upgrade-dialog.png#center"></p>
<p>Now, what this does in the background is just <em>pre-stage</em> the vManage image for an upgrade. The actual software upgrade is not occurring just yet.</p>
<p>Think of this step like you might prepare an IOS/IOS-XE router: Copying the image to the device flash. The image is there and ready - but we haven&rsquo;t booted to it yet.</p>
<p>Once that&rsquo;s all done, we&rsquo;ll go back to the vManage upgrade page and click <strong>Activate</strong>, select our image again, then click <strong>Activate</strong>.</p>
<p>Now <em>this</em> step is where vManage will reboot, apply the upgrade, and come back online with the new image.</p>
<p>Again, back to the IOS/IOS-XE analogy: this is the equivalent of setting out <strong>boot system flash:&lt;image name&gt;</strong> to the new image, then rebooting our router.</p>
<p>vManage may take a short while to complete &amp; reinitialize. In my lab, about 10-15 minutes.</p>
<h2 id="upgrading-vbond--vsmart">Upgrading vBond &amp; vSmart</h2>
<p>After vManage is done, it&rsquo;s time to work on the real heart of our control plane: vBond &amp; vSmart.</p>
<p>Similar to vManage, we&rsquo;ll start by going to <strong>Maintenance</strong> &gt; <strong>Software Upgrades</strong> &gt; <strong>Controller</strong>.</p>
<p>Here we will need to select the devices we want to upgrade. As I mentioned earlier, you may want to do these one at a time &amp; in a phased approach. However, in my lab I&rsquo;ll select all of them to upgrade at once.</p>
<p>Now in this case, vManage will still follow the proper order (vBond, then vSmart), and even perform a rolling upgrade one device at a time. This may be suitable for you in production, but again I would urge you to test it for yourself first!</p>
<p><img alt="controller-upgrade-dialog" loading="lazy" src="/content/images/2020/11/controller-upgrade-dialog.png#center"></p>
<p>The other big difference here, as you can see in the screenshot above, is the presence of the <strong>Activate &amp; Reboot</strong> checkbox.</p>
<p>This does exactly as you would anticipate. Instead of doing the two-step process with vManage where we staged the image, then performed the activation/reboot - this checkbox will do all of that in one step.</p>
<p>In my lab environment, I did check this box &amp; allowed everything to reboot automatically.</p>
<p>Why is there a separation between uploading the image &amp; rebooting / activating it? To allow better granularity over the process.</p>
<p>For example, maybe you have a poor internet connection at a branch site &amp; the image upload may take a long time. This separation of tasks allows you to stage all of the images independently of applying them. If you have a short outage window, this could help you save time by pre-staging the images ahead of time.</p>
<p>Back to the upgrade - just like vManage we&rsquo;ll select the version we&rsquo;re applying then click <strong>Upgrade</strong></p>
<p>Again - Depending on the resources available to your controllers, the image upload / activation process may take a short while&hellip;</p>
<h2 id="upgrading-the-wan-edge-appliances">Upgrading the WAN Edge Appliances</h2>
<p>In my lab, I&rsquo;m currently using a handful of vEdge Cloud VMs as branch office routers. The upgrade process here should apply to other edge devices as well.</p>
<p>After we&rsquo;re confident our controller upgrades have been successful &amp; all control connections have been re-established - we can move to upgrading our edge devices.</p>
<p>It&rsquo;s also worth mentioning that my lab currently has <em>no</em> redundant deployments of WAN edge appliances. All of my test &lsquo;branch offices&rsquo; are single-homed to one vEdge Cloud - so an outage will be required to apply the images.</p>
<p>If you&rsquo;re using a redundant configuration at a remote site, ideally you would upgrade ONE edge device first. Then only upgrade the second after control connections &amp; routing adjacencies had been re-established on the first device. This should allow for an upgrade with minimal downtime.</p>
<p>The process for upgrading the edge devices mirrors what we saw for vBond / vSmart.</p>
<p>We&rsquo;ll go to <strong>Maintenance</strong> &gt; <strong>Software Upgrade</strong> &gt; <strong>WAN Edge</strong>, then select the edge devices we want to upgrade.</p>
<p>Click <strong>Upgrade</strong>, select the target version from the drop-down, then click <strong>Upgrade</strong>. Again, in my case, I also selected the <strong>Activate &amp; Reboot</strong> checkbox</p>
<p><img alt="vedge-upgrade-dialog" loading="lazy" src="/content/images/2020/11/vedge-upgrade-dialog.png#center"></p>
<p><strong>NOTE:</strong> It&rsquo;s worth mentioning that for the WAN Edge upgrade, vManage will push the upgrade to <em>all devices simultaneously</em>. <del>If you would like to perform a rolling upgrade here, you&rsquo;ll have to manage it yourself.</del> In the case of a site where a redundant vEdge is deployed &amp; they share the same site ID, vManage automatically will handle upgrading only one at a time to maintain uptime (Thanks <a href="https://twitter.com/juangolbez">Tim McConnaughy</a> for clarifying this!).</p>
<p>Once again, we&rsquo;ll give the edge devices a few minutes to download &amp; apply their software upgrades. Then we&rsquo;ll check to ensure all of the control connections re-established &amp; traffic is flowing.</p>
<h2 id="wrap-up">Wrap up</h2>
<p>Once your edge devices are back online, it&rsquo;s all done! The network has been upgraded to the new version.</p>
<p>There are a handful of ways to check this, but one easy way is via the device monitor page: <strong>Monitor</strong> &gt; <strong>Network</strong></p>
<p>This page will list a summary of everything in the network, including the current software version &amp; number of established control connections. For me, it&rsquo;s an easy way to get a one-page summary of the network.</p>
<p><img alt="monitor-networkpng" loading="lazy" src="/content/images/2020/11/monitor-networkpng.png#center"></p>
<p>From the screenshot above - we can see that all of my lab devices are back online &amp; running software version 20.3.2.</p>
<hr>
<p>That&rsquo;s it! Hope this post was helpful to you.</p>
<p>Thanks for reading!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Automating the CLI (Part 2): Building a Web Dashboard with Flask &amp; Bootstrap</title>
      <link>https://0x2142.com/web-dashboard-flask-and-bootstrap/</link>
      <pubDate>Mon, 12 Oct 2020 14:42:58 +0000</pubDate>
      <guid>https://0x2142.com/web-dashboard-flask-and-bootstrap/</guid>
      <description>Building on the last post, we&amp;rsquo;ll show how easy it an be to build a Flask dashboard</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/JGvVSxKTy74?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>In the <a href="/automating-the-cli-using-scrapli/">last post</a>, I covered a simple project that I&rsquo;m working on while studying for the Cisco DevNet certifications. This is part two of a series, which will continue on while I work through this project.</p>
<p>As a brief summary - I started using a combination of Scrapli &amp; Cisco Genie for a short network automation script. This script connects to a list of network switches &amp; outputs a spreadsheet of interface statistics. This provides a quick snapshot view into the current port capacity within your network.</p>
<p>In this post, I&rsquo;ll be showing off a bit of how to build a simple web frontend - which I&rsquo;ll put together using <a href="https://flask.palletsprojects.com/en/1.1.x/">Flask</a> and <a href="https://pythonhosted.org/Flask-Bootstrap/">Bootstrap</a>.</p>
<hr>
<h2 id="getting-started-with-flask">Getting Started with Flask</h2>
<p>Okay so I have a bad habit of making a lot of Python scripts which are essentially just command-line utilities.</p>
<p>In theory, this isn&rsquo;t necessarily problem. But what if I want to share some of the tools I build? Maybe not everyone wants to compile config files &amp; figure out what command-line arguments are needed. For some, they may prefer to have an easy-to-use web interface instead.</p>
<p>So for this project I opted to venture a little outside my typical comfort zone &amp; build a web dashboard. I&rsquo;ll admit I&rsquo;ve done this once or twice before, but certainly not something I would claim proficiency in!</p>
<p>The good thing is - building a web interface doesn&rsquo;t have to be complex. My intent with this post is to try &amp; break it down a bit - and hopefully encourage you to try it yourself!</p>
<h3 id="installing-flask--hello-world">Installing Flask &amp; Hello World</h3>
<p>Installing Flask is much like any other python module - we&rsquo;ll use pip:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install flask
</span></span></code></pre></div><p>Once installed, we can import the module into our python script:</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">flask</span> <span class="kn">import</span> <span class="n">Flask</span>
</span></span></code></pre></div><p>Simple enough!</p>
<p>Now let&rsquo;s look at how easy it is to set up the web server &amp; return a simple web page:</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">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s2">&#34;Hello there!&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">app</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</span></span></code></pre></div><p>First we create a new instance of Flask, using the syntax <em>app = Flask(<strong>name</strong>)</em>.</p>
<p>Next, we just create a quick python function to return the text <strong>Hello there!</strong> You&rsquo;ll notice that there is a decorator above the function - this is used by Flask to route incoming HTTP requests.</p>
<p>In this example, we use <em>@app.route(&rsquo;/&rsquo;)</em>. This tells Flask to register this python function for any HTTP calls to <em>http://&lt;url&gt;/</em>. If we wanted a function for any HTTP requests to <em>http://&lt;url&gt;/networking</em>, we would modify the decorator to <em>@app.route(&rsquo;/networking&rsquo;)</em>.</p>
<p>Lastly, we need to start the Flask web server when the script is run. This is accomplished using <em>app.run()</em></p>
<p>Let&rsquo;s go ahead and try to run the script - which should start the Flask server:</p>
<p><img alt="flask-runoutput" loading="lazy" src="/content/images/2020/10/flask-runoutput.PNG#center"></p>
<p>In the above screenshot, you&rsquo;ll see that by default Flask will start on <a href="http://127.0.0.1:5000">http://127.0.0.1:5000</a>. You may also notice that Flask will print out real-time logging of incoming HTTP requests.</p>
<p>If we visit our page, we&rsquo;ll see pretty much what we might expect:</p>
<p><img alt="Hello-world" loading="lazy" src="/content/images/2020/10/Hello-world.PNG#center"></p>
<p>Before we move onto the next section, I did want to show one more example.</p>
<p>Let&rsquo;s say that we have a python variable (or dictionary, etc). Could we pass that as part of our web page function?</p>
<p>Let&rsquo;s look at this modified example:</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">web_page_text</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;Hello&#34;</span><span class="p">:</span> <span class="s2">&#34;There!&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">web_page_text</span>
</span></span></code></pre></div><p>If we reload our local web page, we&rsquo;ll now see the following:</p>
<p><img alt="hello-world-2" loading="lazy" src="/content/images/2020/10/hello-world-2.PNG#center"></p>
<p>Keep this in mind - We&rsquo;ll use this later!</p>
<h3 id="making-life-easier-with-html-templates">Making life easier with HTML templates</h3>
<p>Now then - you could build all of your HTML in python, then return the entire HTML page as a response to the function call. That being said, just because you can doesn&rsquo;t mean you should 🙃</p>
<p>Let&rsquo;s take a quick look at how we can use HTML templates to help build our web page.</p>
<p>First we&rsquo;ll create a new directory called <em>templates</em>, and create an HTML file. In this case, I named my template <em>main.html</em>:</p>
<p><img alt="dir" loading="lazy" src="/content/images/2020/10/dir.PNG#center"></p>
<p>To start with, we&rsquo;ll just add some simple text into the HTML file:</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">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    Hello There - From the template!
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>On the Flask side, we&rsquo;ll have to modify our imports to include the <em>render_template</em> module. We&rsquo;ll also update what we&rsquo;re returning when an HTTP request is received:</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">flask</span> <span class="kn">import</span> <span class="n">Flask</span><span class="p">,</span> <span class="n">render_template</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s1">&#39;main.html&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">app</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</span></span></code></pre></div><p>So now instead of returning plain text, we&rsquo;ll return <em>render_template(&lsquo;main.html&rsquo;)</em>.</p>
<p>Flask uses Jinja2 for templating, which we&rsquo;ll get into shortly. The above function call tells our app to use <em>main.html</em> as a base template for the content that is returned to the end user.</p>
<p>Let&rsquo;s check out the page:</p>
<p><img alt="hello-world-3" loading="lazy" src="/content/images/2020/10/hello-world-3.PNG#center"></p>
<p>Awesome - we receive our HTML file as a response this time.</p>
<h3 id="extending-functionality-with-jinja2">Extending functionality with Jinja2</h3>
<p>Now we can start getting into the fun stuff.</p>
<p>Manually writing out a bunch of HTML is fun and all - but what if we could auto-generate everything programmatically?</p>
<p>Jinja2 allows for exactly that. We&rsquo;ll be able to insert variables, if/then statements, and looping logic directly into our HTML template.</p>
<p>As an example, let&rsquo;s say we had the following python dictionary - which we wanted to display on a web page:</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">switchList</span> <span class="o">=</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="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;C9200&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;serial&#34;</span><span class="p">:</span> <span class="s2">&#34;ABCD00010&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ip&#34;</span><span class="p">:</span> <span class="s2">&#34;192.168.1.1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;check&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;total&#34;</span><span class="p">:</span> <span class="mi">28</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;up&#34;</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;down&#34;</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;disabled&#34;</span><span class="p">:</span> <span class="mi">13</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;capacity&#34;</span><span class="p">:</span> <span class="mi">35</span>       
</span></span><span class="line"><span class="cl">    <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="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;C9300&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;serial&#34;</span><span class="p">:</span> <span class="s2">&#34;ABCD00011&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ip&#34;</span><span class="p">:</span> <span class="s2">&#34;172.168.44.2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;check&#34;</span><span class="p">:</span> <span class="kc">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;total&#34;</span><span class="p">:</span> <span class="mi">48</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;up&#34;</span><span class="p">:</span> <span class="mi">32</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;down&#34;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;disabled&#34;</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;capacity&#34;</span><span class="p">:</span> <span class="mi">67</span>       
</span></span><span class="line"><span class="cl">    <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="s2">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;C9400&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;serial&#34;</span><span class="p">:</span> <span class="s2">&#34;ABCD00012&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ip&#34;</span><span class="p">:</span> <span class="s2">&#34;172.33.44.2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;check&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;total&#34;</span><span class="p">:</span> <span class="mi">48</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;up&#34;</span><span class="p">:</span> <span class="mi">40</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;down&#34;</span><span class="p">:</span> <span class="mi">6</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;disabled&#34;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;capacity&#34;</span><span class="p">:</span> <span class="mi">82</span>       
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>This dictionary is an example from the scrapli script I&rsquo;ve been working on (more detail in my <a href="/automating-the-cli-using-scrapli/">last post</a>).</p>
<p>One way for us to present this using an HTML template would be the following:</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">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    {% block content %}
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    {% for switch in switches %}
</span></span><span class="line"><span class="cl">    Switch Name: {{ switch.name }} 
</span></span><span class="line"><span class="cl">      - Serial: {{ switch.serial }} 
</span></span><span class="line"><span class="cl">      - Reachable at: {{ switch.ip }} 
</span></span><span class="line"><span class="cl">      
</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">    {% endblock %}
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>As you can see - that&rsquo;s a bit of a change from what we were using in our template before.</p>
<p>The Jinja2 syntax above uses a combination of curly braces &amp; percentage signs to indicate which parts of our template are logic that should be processed before returning content to the user. Variables are represented with double curly braces.</p>
<p>Once our template receives the <em>switchList</em> dictionary, we can iterate through it similar to how we might in python. Using the <em>{% for switch in switches %}</em> syntax, we can iterate through each item - and pull out relevant data.</p>
<p>Within our for loop, we can reference the individual values of each item in the dictionary. Using the double braces, we can insert a placeholder for where a piece of content should be inserted. For example, in the above template we have a spot where we want to insert the name of the switch. We use <em>{{ switch.name }}</em> as a placeholder, where the value from the dictionary will be inserted once our template is processed.</p>
<p>Okay - so before we test this, we have one minor modification to our python function:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s1">&#39;main.html&#39;</span><span class="p">,</span> <span class="n">switches</span><span class="o">=</span><span class="n">switchList</span><span class="p">)</span>
</span></span></code></pre></div><p>We&rsquo;ve changed our function <em>render_template</em>, but also include the python object that we&rsquo;ll be passing to our template.</p>
<p>Let&rsquo;s test this out:</p>
<p><img alt="switchdict" loading="lazy" src="/content/images/2020/10/switchdict.PNG#center"></p>
<p>Now there&rsquo;s some magic! Using a combination of python &amp; our Jinja2 templates - we can drastically reduce the amount of HTML code that we need to write.</p>
<h3 id="okay-but-thats-not-pretty">Okay, but that&rsquo;s not pretty</h3>
<p>Yes I know. But we needed to get the basics done first!</p>
<p>Here&rsquo;s where Bootstrap comes in.</p>
<p>I went out to Google and searched for good CSS templates. I&rsquo;m not a web developer, nor am I good at design - so I&rsquo;ll leave that work to someone else.</p>
<p>In my searching, I found quite a bunch of free templates&hellip; but I fell in love with one called Lux by <a href="https://bootswatch.com/">Bootswatch</a>.</p>
<p>They provide a great <a href="https://bootswatch.com/lux/">sample page</a> for the template, which shows off different variations. But the best part is that it also includes code samples!</p>
<p>After we pick &amp; download our CSS template, we&rsquo;ll place it in a new directory named <em>static</em>:</p>
<p><img alt="dir2" loading="lazy" src="/content/images/2020/10/dir2.PNG#center"></p>
<p>To get started, we&rsquo;ll need to install one additional python module:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install flask_bootstrap
</span></span></code></pre></div><p>Then we&rsquo;ll also need a quick modification to our base Flask app:</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">flask_bootstrap</span> <span class="kn">import</span> <span class="n">Bootstrap</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">...</span> <span class="n">code</span> <span class="n">omitted</span> <span class="o">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">Bootstrap</span><span class="p">(</span><span class="n">app</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">app</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
</span></span></code></pre></div><p>I removed some code to focus on just the two parts that change.</p>
<p>First, we need to import our new module. Second, we need to inject our bootstrap plugin into the Flask app. We do this with <em>Bootstrap(app)</em> prior to <em>app.run()</em>.</p>
<p>Let&rsquo;s move back over to our HTML template, since this is where the most of our changes will be.</p>
<p>First, we&rsquo;ll need to include a few things to make sure our CSS file is loaded:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl">{%- extends &#34;bootstrap/base.html&#34; %}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    {% block styles %}
</span></span><span class="line"><span class="cl">    {{super()}}
</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></span><span class="line"><span class="cl">            <span class="na">href</span><span class="o">=</span><span class="s">&#34;{{url_for(&#39;static&#39;, filename=&#39;bootstrap.css&#39;)}}&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    {% endblock %}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>I won&rsquo;t dive in too deep on this piece, as it&rsquo;s fairly straightforward. First, we&rsquo;ll need to include the base/default bootstrap template. Next, we&rsquo;ll include a reference to where our CSS file is located &amp; it&rsquo;s name - so that it gets loaded when our page is rendered.</p>
<p>Okay, now for the body of the page:</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">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    {% block content %}
</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;table table-hover&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">thead</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="na">scope</span><span class="o">=</span><span class="s">&#34;col&#34;</span><span class="p">&gt;</span>Name<span class="p">&lt;/</span><span class="nt">th</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="na">scope</span><span class="o">=</span><span class="s">&#34;col&#34;</span><span class="p">&gt;</span>Current Capacity<span class="p">&lt;/</span><span class="nt">th</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">thead</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">tbody</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">          {% for switch in switches %}
</span></span><span class="line"><span class="cl">            {% if switch.capacity &gt; 75 %}
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">tr</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;table-danger&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            {% else %}
</span></span><span class="line"><span class="cl">                <span class="p">&lt;</span><span class="nt">tr</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;table-success&#34;</span><span class="p">&gt;</span>
</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">th</span> <span class="na">scope</span><span class="o">=</span><span class="s">&#34;row&#34;</span><span class="p">&gt;</span>{{ switch.name }}<span class="p">&lt;/</span><span class="nt">th</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span>{{ switch.capacity }}%<span class="p">&lt;/</span><span class="nt">td</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">          {% endfor %}
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">tbody</span><span class="p">&gt;</span>
</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">    {% endblock %}
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span></code></pre></div><p>Now instead of just returning a plain-text list of all three switches, we&rsquo;ll use our CSS template to generate a colorful table. For the purpose of this example, I kept the data to just showing the switch name &amp; it&rsquo;s current capacity.</p>
<p>Most of the template is fairly simple. First we build our table header using the <em>&lt;thead&gt;</em> tags. Next we&rsquo;ll build the rows of our table using <em>&lt;tbody&gt;</em> and <em>&lt;tr&gt;</em>.</p>
<p>For the Jinja2 logic - We&rsquo;ll iterate through our list of switches, and add a new table row (<em>&lt;tr&gt;</em>) for each device. I also added logic to check the current capacity of the switch. If the switch capacity is greater than 75%, we create the table row using <em>&lt;tr class=&ldquo;table-danger&rdquo;&gt;</em> - which is a reference in our CSS stylesheet that will color the row red. If capacity is less than 75%, we use <em>table-success</em> for a green color.</p>
<p>Let&rsquo;s go ahead and check out what this looks like:</p>
<p><img alt="table" loading="lazy" src="/content/images/2020/10/table.PNG#center"></p>
<p>And as we expect - we get our nicely formatted table &amp; the correct colors on each row.</p>
<h2 id="bringing-it-all-together">Bringing it all together</h2>
<p>Now that we&rsquo;ve gotten some background on how to use Flask &amp; Bootstrap, let me show you a bit of what I&rsquo;ve been working on.</p>
<p>I won&rsquo;t cover much code in this section, but you can check out the <a href="https://github.com/0x2142/switchport-web-dashboard">GitHub repo</a> if you&rsquo;re interested in seeing the details.</p>
<p>The scrapli script that I wrote previously has been extended a little. The biggest change is that instead of writing its output to a spreadsheet, it will insert the data into a sqlite database.</p>
<p>The web frontend has been assembled using the same basic ideas covered in this blog (plus a lot of banging my head against the desk, trying to figure out why things don&rsquo;t format or align properly 🙂). Whenever a request is made to the web dashboard, it will query the sqlite database for the requested information.</p>
<p>So, without further delay - here it is!</p>
<p><img alt="dashboard-01" loading="lazy" src="/content/images/2020/10/dashboard-01.PNG#center"></p>
<p>I currently have one physical device running, plus a handful of virtual nexus 9k &amp; CSRv devices running in Cisco Modeling Labs. So all of the data shown in this dashboard is being pulled from real(ish) equipment - no mock data.</p>
<p>This was one of those projects where I started off thinking my dashboard was going to be really simple&hellip; but then I kept having new ideas and getting carried away with the design &amp; functionality.</p>
<p>The table above shows each switch, it&rsquo;s serial number, management IP, current software version, and the port inventory. Similar to the example earlier in this post, I have a small progress bar that shows switch capacity - and will change color depending on percentage of in-use ports.</p>
<p>If you click on any of the switch names, you&rsquo;ll be taken to a page with additional detail on that particular device:</p>
<p><img alt="dashboard-02" loading="lazy" src="/content/images/2020/10/dashboard-02.PNG#center"></p>
<p>On this first tab, you&rsquo;ll see some quick info - mostly the same that is on the main page of the dashboard. However, I added two additional tabs here - one for a detailed port breakdown &amp; one for a dump of the raw CLI output.</p>
<p>If we click on the port info:</p>
<p><img alt="dashboard-03" loading="lazy" src="/content/images/2020/10/dashboard-03.PNG#center"></p>
<p>We have a breakdown of how many ports are currently in use vs down, as well as our breakdown of port media &amp; operational speed. Since I account for switches that are anywhere from 10M up to 100G, the data for operational speeds will only show the speeds that are actually in-use on the switch.</p>
<p>Just in case it&rsquo;s needed, I added a tab to show the raw CLI output from the switch:</p>
<p><img alt="dashboard-04" loading="lazy" src="/content/images/2020/10/dashboard-04.PNG#center"></p>
<p>This could be a quick way to check port counters &amp; errors, or any other information that isn&rsquo;t already presented via the dashboard.</p>
<p>Lastly, I built a separate page to provide statistics on ALL devices in the network:</p>
<p><img alt="dashboard-05" loading="lazy" src="/content/images/2020/10/dashboard-05.PNG#center"></p>
<p>This page will show port count &amp; availability network-wide. However, I also added some spaces to show the top 5 hardware models &amp; software versions that are in-use across the network.</p>
<hr>
<p>I went into this project a bit worried because frontend development isn&rsquo;t my strength. That being said, I really enjoyed working on this project. It was a different challenge than I&rsquo;m normally used to - and it gave me a creative way to try and format &amp; display the data I am collecting.</p>
<p>I hope this post was useful to you. Please leave a comment below - and check out my <a href="https://github.com/0x2142/switchport-web-dashboard">Github repo</a> for the full code.</p>
]]></content:encoded>
    </item>
    <item>
      <title>An Afternoon with ARIN</title>
      <link>https://0x2142.com/an-afternoon-with-arin/</link>
      <pubDate>Tue, 15 Sep 2020 11:30:03 +0000</pubDate>
      <guid>https://0x2142.com/an-afternoon-with-arin/</guid>
      <description>Attending a local ARIN event left me with some thoughts&amp;hellip;</description>
      <content:encoded><![CDATA[<p>I had the opportunity to attend an <a href="https://www.arin.net/participate/meetings/on-the-road">ARIN on the Road</a> event last week. It was an all-day event that focused on education: who ARIN is, what they do, and some things they are working on. As a network admin I&rsquo;ve had to work with ARIN a handful of times to request network resources. I figured it would be a good experience to attend one of these events and see what ARIN has to say. I actually found out about a few things I wasn&rsquo;t aware of previously, so this post is going to be a brief summary of what I learned.</p>
<h2 id="about-arin">About ARIN</h2>
<p>If you haven&rsquo;t already worked with them - <a href="https://www.arin.net/">ARIN</a> is the American Registry for Internet Numbers. They are a non-profit organization and their purpose is to assign/manage Internet number resources for all of North America. This includes IPv4/IPv6 addresses and BGP Autonomous System Numbers (ASNs). ARIN is one of five Regional Internet Registries (RIRs) - each managing Internet resources for it&rsquo;s own individual region. All of these report back to a top-level organization, the Internet Assigned Numbers Authority (IANA).</p>
<p>What I didn&rsquo;t know: ARIN actually used to manage resources for all of South America and Africa as well. LACNIC formed and took ownership of South America in 2001, and AFRINIC took Africa in late 2004. ARIN itself has only been around since 1997, and will be celebrating it&rsquo;s 20th anniversary this December.</p>
<p>Outside of assigning/managing number resources - ARIN manages a huge manual of numbering policies and standards (<a href="https://www.arin.net/policy/nrpm.html">The Number Resource Policy Manual</a>). A good note here is that these policies are heavily influenced by the community - so if any individual or group of network operators want to change/modify or add new policies, then they can submit proposals to do so.</p>
<h2 id="ipv4-depletion">IPv4 Depletion</h2>
<p>I was very interested to hear about what&rsquo;s going on with IPv4/IPv6 - mostly because I&rsquo;ve been trying to push for IPv6 in many of the places I have worked. The ARIN group spent a little bit of time talking about how the depletion of IPv4 addresses has affected their workload. Overall, it seems like their work has remained about the same - but it has transitioned from mostly IPv4 allocations to more IPv4 transfer requests.</p>
<p>An interesting note from this discussion was that ARIN only performs the backend registration changes for IPv4 block transfers. They play no part in the actual negotiations between two organizations. However, they do perform their own investigations during transfers to ensure that the source organization legitimately owns the IP block, and the destination organization can justify the use of the space.</p>
<p>I had heard previously that ARIN kept a block of IPv4 addresses for transition to IPv6 - but I never researched it further. This was a topic ARIN touched on during the event. Essentially, they have kept ownership of a /10 block of addresses, which is split up into individual /24 blocks for assignment. Any organization can request one of the /24s when they request a block of IPv6 addresses. The organization must fill out a justification form, in which they demonstrate how the IPv4 blocks will be used to help transition to IPv6. Organizations can request one of these blocks every 6 months, provided they can still justify the need for them. This is all documented in NRPM section <a href="https://www.arin.net/policy/nrpm.html#four10">4.10</a>.</p>
<p>The somewhat surprising thing here is that ARIN was actively encouraging people to take advantage of this. Probably because they need to push IPv6 adoption in any way they can. As of the date of the event, ARIN stated that only ~60 /24 blocks had been assigned so far.</p>
<h2 id="ipv6-adoption">IPv6 Adoption</h2>
<p>This part of the event wasn&rsquo;t quite everything I wanted it to be. Overall ARIN touched on statistics from Google and other organizations that show the trending uptake in IPv6 network access. They also spoke briefly about how the structure of IPv6 addresses makes life easier - because the last 64 bits can always be used for host-based MAC autoconfig, then network operators only worry about subnetting above that.</p>
<p>Interestingly enough, ARIN was advocating for the method of &lsquo;assign way more addresses than you&rsquo;ll ever need&rsquo; mentality for IPv6. Another attendee asked the question &lsquo;Won&rsquo;t we run into the same thing as IPv4, if we just throw out v6 blocks like candy&rsquo;? This actually led to hearing something I wasn&rsquo;t aware of - IANA has currently only made 1/8th of IPv6 blocks public available for use. The current numbering scheme/standard will be used for this first block of addresses. If we run through them too quickly, then we can step back and re-evaluate best practices before handing out the next 1/8th block of addresses.</p>
<h2 id="dnssec">DNSSec</h2>
<p>Initially I was a bit confused that DNSSec was on the topic list - but I figured maybe ARIN was just trying to push this for the betterment of the Internet. While they spoke a bit about DNSSec for forward DNS, their primary topic was how DNSSec for reverse DNS isn&rsquo;t something people are normally thinking about. As it turns out, ARIN offers reverse-lookup DNSSec for any IP blocks that they assign out. This is good to know, since reverse DNS can be important for things like email security - and its certainly something I&rsquo;ve never really considered in the past.</p>
<p>If you have purchased IPv4/v6 blocks directly from ARIN - I would recommend that you check this out.</p>
<h2 id="rpki">RPKI</h2>
<p>Resource Public Key Infrastructure (RPKI) is a way of cryptographically validating ownership of IP address space or routing objects. Since BGP is primarily a trust-based protocol between organizations, RPKI allows network operators to implement additional security by providing a certificate-based system of trust. The majority of this discussion was around how bad BGP security is, and that overall North America is far behind on implementing RPKI.</p>
<p>ARIN has a service available where they will act as your Certificate Authority (CA) for RPKI - so it only requires network operators to sign records then implement a few device changes.</p>
<h2 id="my-thoughts">My Thoughts</h2>
<p>Overall the event was fairly informative! It wasn&rsquo;t quite everything I wanted it to be, but I did walk away with additional knowledge that I didn&rsquo;t have before. I was really hoping to learn more about how other organizations are implementing IPv6, or even how other people are convincing their employers to take IPv6 adoption seriously. When I spoke with some other attendees, it seemed like not many people had IPv6 running in a production environment yet - only a few of them had even started testing. Surprisingly, even the ARIN reps were repeatedly asking people to contact them if they had an IPv6 success story to share.</p>
<p>One thing I found really interesting was surrounding DNSSec/RPKI. A few attendees asked about how many people are actually validating signed resources. It&rsquo;s one thing to implement signing, but it won&rsquo;t matter if no one validates the resources, right? Surprisingly, ARIN had no statistics about this - and stated the point that they cannot enforce adoption of these standards. It certainly makes sense, but it&rsquo;s not something I gave much thought to previously. Since they&rsquo;re just a registry, they can only make these services available - not enforce their usage. This is why they put on events such as this to raise awareness and provide education.</p>
<p>ARIN pushed the fact that all of their policies are community driven. There were quite a few examples throughout the event of how individual members of the community could impact changes to their policies. My primary concern is that it seemed like a majority of the individuals in attendance represented government or educational organizations - and not a lot who worked in similar network environments to what I manage. They raised their own concerns and questions, which were certainly valid for the types of infrastructure and designs that they maintain. However, a number of these things don&rsquo;t really apply to my infrastructure in quite the same ways.</p>
<p>If I have to make one point here: If you&rsquo;re a network operator, go subscribe to ARINs <a href="https://www.arin.net/participate/mailing_lists/">mailing lists</a> and get involved. Maybe you don&rsquo;t have any ideas for policy changes, but you never know what might come up that you could provide meaningful input on. The ARIN reps provided an example or two of when a smaller group of people suggested policy changes which drastically affected bigger companies - and almost no one opposed it until it took effect. Only you have the ability to voice your opinion and concerns about how a proposed policy could affect your network. If not, the next time you try to request a block of IP addresses or a BGP ASN, you could potentially run into roadblocks because of a policy change proposed by someone with very different needs.</p>
<p>The staff at ARIN don&rsquo;t live and work in the networks that we do. They try to work with network operators to understand use cases and the possible ramifications of policy changes - but ultimately they are a small non-profit. They can&rsquo;t think of everything, nor can they force network operators to contribute their opinions. Get involved and make a difference.</p>
<p>As a final note, ARIN has a <a href="https://www.arin.net/participate/meetings/fellowship.html">Fellowship Program</a> where you can apply to attend one of their Public Policy meetings for free. Fill out an application and if you&rsquo;re chosen they&rsquo;ll provide a ticket, hotel room, and travel expenses. It&rsquo;s a great opportunity to experience one of these meetings, especially if you might not have the financial means to otherwise.</p>
<hr>
<p>The slide deck from the event is publicly available on ARIN&rsquo;s website: <a href="https://www.arin.net/vault/participate/meetings/on-the-road/presentations/columbus_2017.pdf">here</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Automating the CLI: Using Scrapli &amp; Cisco Genie to Collect Interface Data</title>
      <link>https://0x2142.com/automating-the-cli-using-scrapli/</link>
      <pubDate>Tue, 08 Sep 2020 13:29:25 +0000</pubDate>
      <guid>https://0x2142.com/automating-the-cli-using-scrapli/</guid>
      <description>Let&amp;rsquo;s look at one way to automate routine CLI-based tasks using Scrapli &amp;amp; Cisco Genie</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/79WitHmGW9I?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>So I just scheduled an upcoming attempt at the <strong>Developing Applications using Cisco Core Platforms and APIs (DEVCOR 350-901)</strong> exam.</p>
<p>It&rsquo;s coming up quick in two weeks here &amp; I&rsquo;m starting to do some review / final prep before taking the exam.</p>
<p>Why does this matter? Well - I plan on doing a few write-ups &amp; videos on some of the content I&rsquo;m studying. There are a lot of people diving into the DevNet exams, and I want to do what I can to help other people succeed.</p>
<p>All that being said, check back here over the next few weeks - or subscribe on <a href="https://www.youtube.com/channel/UCTY24K3UQrc6xHFcLfGg1hg">YouTube</a> - to see the additional content that will follow. While this specific blog doesn&rsquo;t cover much from the exam blueprint, it&rsquo;s the beginning a simple project I&rsquo;ll be using in the further content.</p>
<hr>
<h2 id="getting-started-with-scrapli">Getting Started with Scrapli</h2>
<p>For this project I opted to start with scrapli, and possibly migrate to using RESTCONF later on in the process. Mostly this was an excuse for me to try out scrapli, as I&rsquo;ve heard a few people using it recently &amp; was interested.</p>
<p><a href="https://github.com/carlmontanari/scrapli">Scrapli</a> is a screen scraping module for Python. If you&rsquo;re not familiar with screen scraping, it&rsquo;s the process of connecting to something via telnet/ssh/etc, then literally scraping or dumping the contents of the screen. There are other modules that do this as well, like paramiko, netmiko, or expect scripting.</p>
<p>In networking, too many of our devices still rely on CLI and don&rsquo;t have proper API endpoints. We&rsquo;re working on it, but yet still a ways from having it everywhere. So in the meantime, we still need screen scraping for automation.</p>
<p>On the whole, this isn&rsquo;t necessarily a bad thing. For example, most network engineers are very familiar and competent with the CLI of any network operating system. It&rsquo;s easier to jump into the world of automation if you can start with something you know, the CLI. It&rsquo;s harder to force someone to start their automation journey by giving up everything they know and shoving REST APIs at them.</p>
<p>Okay - enough rambling. Let&rsquo;s get scrapli installed and see what it can do.</p>
<h3 id="installing-the-module">Installing the module</h3>
<p>Easiest part of the whole project. Install with pip:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install scrapli
</span></span></code></pre></div><p>Next we&rsquo;ll go ahead &amp; import into our script.</p>
<p>Scrapli supports quite a handful of operating systems (including a few non-Cisco platforms as well!). In the case of my project though, I&rsquo;m only using Cisco IOS-XE switches - so we&rsquo;ll only import the scrapli driver for that.</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">scrapli.driver.core</span> <span class="kn">import</span> <span class="n">IOSXEDriver</span>
</span></span></code></pre></div><h3 id="connecting-to-a-device">Connecting to a Device</h3>
<p>Okay - now that we&rsquo;re all setup with the module, it&rsquo;s time to get working!</p>
<p>First thing, we need to authenticate to our device. Building off of the example code from the scrapli page - we&rsquo;ll create a short dictionary of authentication parameters first:</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">switch</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;host&#34;</span><span class="p">:</span> <span class="s2">&#34;10.1.1.1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;auth_username&#34;</span><span class="p">:</span> <span class="s2">&#34;net_api&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;auth_password&#34;</span><span class="p">:</span> <span class="s2">&#34;net_api_pass&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;auth_strict_key&#34;</span><span class="p">:</span> <span class="kc">False</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">cli</span> <span class="o">=</span> <span class="n">IOSXEDriver</span><span class="p">(</span><span class="o">**</span><span class="n">switch</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">cli</span><span class="o">.</span><span class="n">open</span><span class="p">()</span>
</span></span></code></pre></div><p>Once we build out dictionary, we&rsquo;ll use <strong>**switch</strong> to unpack our key/value pairs into the IOSXEDriver object &amp; assign it to a variable called <em>cli</em>. Then, all we need to do is call <em>cli.open()</em> and scrapli will open a connection to our target device.</p>
<p>Now we can run any command we want by calling <em>cli.send_command()</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="n">sh_int</span> <span class="o">=</span> <span class="n">cli</span><span class="o">.</span><span class="n">send_command</span><span class="p">(</span><span class="s2">&#34;show interface&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sh_int</span><span class="o">.</span><span class="n">output</span><span class="p">)</span>
</span></span></code></pre></div><p>So in the above example, I want to issue a <em>show interface</em> - then print the output.</p>
<p>What we get is shown below:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">GigabitEthernet1/0/1 is up, line protocol is up (connected)
</span></span><span class="line"><span class="cl">  Hardware is Gigabit Ethernet, address is 78bc.1a81.e101 (bia 78bc.1a81.e101)
</span></span><span class="line"><span class="cl">  Description: Test Port
</span></span><span class="line"><span class="cl">  MTU 1500 bytes, BW 1000000 Kbit/sec, DLY 10 usec,
</span></span><span class="line"><span class="cl">     reliability 255/255, txload 1/255, rxload 1/255
</span></span><span class="line"><span class="cl">  Encapsulation ARPA, loopback not set
</span></span><span class="line"><span class="cl">  Keepalive set (10 sec)
</span></span><span class="line"><span class="cl">  Full-duplex, 1000Mb/s, media type is 10/100/1000BaseTX
</span></span><span class="line"><span class="cl">  input flow-control is off, output flow-control is unsupported
</span></span><span class="line"><span class="cl">  ARP type: ARPA, ARP Timeout 04:00:00
</span></span><span class="line"><span class="cl">  Last input 00:00:11, output 00:00:03, output hang never
</span></span><span class="line"><span class="cl">  Last clearing of &#34;show interface&#34; counters never
</span></span><span class="line"><span class="cl">  Input queue: 0/2000/0/0 (size/max/drops/flushes); Total output drops: 198
</span></span><span class="line"><span class="cl">  Queueing strategy: fifo
</span></span><span class="line"><span class="cl">  Output queue: 0/40 (size/max)
</span></span><span class="line"><span class="cl">  5 minute input rate 9000 bits/sec, 11 packets/sec
</span></span><span class="line"><span class="cl">  5 minute output rate 2066000 bits/sec, 221 packets/sec
</span></span><span class="line"><span class="cl">     2647548 packets input, 371883264 bytes, 0 no buffer
</span></span><span class="line"><span class="cl">     Received 6985 broadcasts (6757 multicasts)
</span></span><span class="line"><span class="cl">     0 runts, 0 giants, 0 throttles
</span></span><span class="line"><span class="cl">     0 input errors, 0 CRC, 0 frame, 0 overrun, 0 ignored
</span></span><span class="line"><span class="cl">     0 watchdog, 6757 multicast, 0 pause input
</span></span><span class="line"><span class="cl">     0 input packets with dribble condition detected
</span></span><span class="line"><span class="cl">     50905390 packets output, 60134059101 bytes, 0 underruns
</span></span><span class="line"><span class="cl">     0 output errors, 0 collisions, 2 interface resets
</span></span><span class="line"><span class="cl">     0 unknown protocol drops
</span></span><span class="line"><span class="cl">     0 babbles, 0 late collision, 0 deferred
</span></span><span class="line"><span class="cl">     0 lost carrier, 0 no carrier, 0 pause output
</span></span><span class="line"><span class="cl">     0 output buffer failures, 0 output buffers swapped out
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[**Output Truncated**]
</span></span></code></pre></div><p>What&rsquo;s that look like? Exactly the same output we would get if we entered <em>show interface</em> on the command line ourselves! (Only Gig1/0/1 shown here to keep this short &amp; clean)</p>
<h3 id="using-cisco-genie-to-parse-cli-output">Using Cisco Genie to Parse CLI Output</h3>
<p>So what if we wanted to pull a list of every interface on the switch? Maybe tally how many ports are connected vs down vs admin disabled? Or even check operational speeds &amp; media types?</p>
<p>Well that&rsquo;s what I&rsquo;m looking to do for this project.</p>
<p>Now at first it may seem like we have to use regular expressions to try &amp; match the content we need from the output. However, there is an easier way!</p>
<p>Cisco Genie is an open source project, and part of the larger <a href="https://developer.cisco.com/pyats/">PyATS</a> automated network testing suite. If you haven&rsquo;t looked at any of that yet, I would highly recommend you check it out.</p>
<p>So what&rsquo;s Genie do? It&rsquo;s a utility that handles all the output parsing for us. There are a ton of pre-existing parsers written, which handle all the hard work so we don&rsquo;t have to.</p>
<p>Best yet - scrapli has native integration into Genie. All we have to do is install one additional component:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install scrapli<span class="o">[</span>genie<span class="o">]</span>
</span></span></code></pre></div><p>And to make use of the power of Genie, we just need to pass our raw output to it.</p>
<p>So our new code will 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">sh_int</span> <span class="o">=</span> <span class="n">cli</span><span class="o">.</span><span class="n">send_command</span><span class="p">(</span><span class="s2">&#34;show interface&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">sh_int_parsed</span> <span class="o">=</span> <span class="n">sh_int</span><span class="o">.</span><span class="n">genie_parse_output</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">sh_int_parsed</span><span class="p">)</span>
</span></span></code></pre></div><p>That&rsquo;s it. And now, instead of that raw output - we get a native Python dictionary that&rsquo;s ready to consume by our script:</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></span><span class="line"><span class="cl">   <span class="nt">&#34;GigabitEthernet1/0/1&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;port_channel&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;port_channel_member&#34;</span><span class="p">:</span><span class="err">False</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;enabled&#34;</span><span class="p">:</span><span class="err">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;line_protocol&#34;</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="nt">&#34;oper_status&#34;</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="nt">&#34;connected&#34;</span><span class="p">:</span><span class="err">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span><span class="s2">&#34;Gigabit Ethernet&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;mac_address&#34;</span><span class="p">:</span><span class="s2">&#34;78bc.1a81.e101&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;phys_address&#34;</span><span class="p">:</span><span class="s2">&#34;78bc.1a81.e101&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;description&#34;</span><span class="p">:</span><span class="s2">&#34;Test Port&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;delay&#34;</span><span class="p">:</span><span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;mtu&#34;</span><span class="p">:</span><span class="mi">1500</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;bandwidth&#34;</span><span class="p">:</span><span class="mi">1000000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;reliability&#34;</span><span class="p">:</span><span class="s2">&#34;255/255&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;txload&#34;</span><span class="p">:</span><span class="s2">&#34;1/255&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;rxload&#34;</span><span class="p">:</span><span class="s2">&#34;1/255&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;encapsulations&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;encapsulation&#34;</span><span class="p">:</span><span class="s2">&#34;arpa&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;keepalive&#34;</span><span class="p">:</span><span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;duplex_mode&#34;</span><span class="p">:</span><span class="s2">&#34;full&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;port_speed&#34;</span><span class="p">:</span><span class="s2">&#34;1000mb/s&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;media_type&#34;</span><span class="p">:</span><span class="s2">&#34;10/100/1000BaseTX&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;flow_control&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;receive&#34;</span><span class="p">:</span><span class="err">False</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;send&#34;</span><span class="p">:</span><span class="err">False</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;arp_type&#34;</span><span class="p">:</span><span class="s2">&#34;arpa&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;arp_timeout&#34;</span><span class="p">:</span><span class="s2">&#34;04:00:00&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;last_input&#34;</span><span class="p">:</span><span class="s2">&#34;00:00:05&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;last_output&#34;</span><span class="p">:</span><span class="s2">&#34;00:00:08&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;output_hang&#34;</span><span class="p">:</span><span class="s2">&#34;never&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;queues&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;input_queue_size&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;input_queue_max&#34;</span><span class="p">:</span><span class="mi">2000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;input_queue_drops&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;input_queue_flushes&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;total_output_drop&#34;</span><span class="p">:</span><span class="mi">198</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;queue_strategy&#34;</span><span class="p">:</span><span class="s2">&#34;fifo&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;output_queue_size&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;output_queue_max&#34;</span><span class="p">:</span><span class="mi">40</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;counters&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;rate&#34;</span><span class="p">:{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;load_interval&#34;</span><span class="p">:</span><span class="mi">300</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;in_rate&#34;</span><span class="p">:</span><span class="mi">8000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;in_rate_pkts&#34;</span><span class="p">:</span><span class="mi">11</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;out_rate&#34;</span><span class="p">:</span><span class="mi">1868000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;out_rate_pkts&#34;</span><span class="p">:</span><span class="mi">205</span>
</span></span><span class="line"><span class="cl">         <span class="p">},</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;last_clear&#34;</span><span class="p">:</span><span class="s2">&#34;never&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_pkts&#34;</span><span class="p">:</span><span class="mi">2658450</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_octets&#34;</span><span class="p">:</span><span class="mi">372811780</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_no_buffer&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_multicast_pkts&#34;</span><span class="p">:</span><span class="mi">6783</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_broadcast_pkts&#34;</span><span class="p">:</span><span class="mi">6783</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_runts&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_giants&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_throttles&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_errors&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_crc_errors&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_frame&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_overrun&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_ignored&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_watchdog&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_mac_pause_frames&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;in_with_dribble&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;out_pkts&#34;</span><span class="p">:</span><span class="mi">51057453</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;out_octets&#34;</span><span class="p">:</span><span class="mi">60309072263</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;out_underruns&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;out_errors&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;out_interface_resets&#34;</span><span class="p">:</span><span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;out_collision&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;out_unknown_protocl_drops&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;out_babble&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;out_late_collision&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;out_deferred&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;out_lost_carrier&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;out_no_carrier&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;out_mac_pause_frames&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;out_buffer_failure&#34;</span><span class="p">:</span><span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="nt">&#34;out_buffers_swapped&#34;</span><span class="p">:</span><span class="mi">0</span>
</span></span><span class="line"><span class="cl">      <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="p">}</span>
</span></span></code></pre></div><p>This format makes our lives a lot easier. For example, let&rsquo;s say we wanted to determine the operational state of a port. Without the Genie integration, we would need to write some regex to comb through the raw output &amp; find what we needed.</p>
<p>However, with Genie involved - its as easy as 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="nb">print</span><span class="p">(</span><span class="n">sh_int_parsed</span><span class="p">[</span><span class="s1">&#39;GigabitEthernet1/0/1&#39;</span><span class="p">][</span><span class="s1">&#39;oper_status&#39;</span><span class="p">])</span>
</span></span></code></pre></div><h3 id="collecting-the-useful-bits">Collecting the Useful Bits</h3>
<p>Now that we have everything in an easy-to-use format, all we need to do is write a few loops to run through our interface list &amp; collect data.</p>
<p>The purpose of my script is to automate the collection of port utilization. For example, think about performing a switch refresh or some form of capacity planning scenario. You might need to inventory how many switches you have, how many ports are utilized vs how many are available, or count the number of copper vs fiber ports.</p>
<p>For this first iteration, we&rsquo;ll just collect the data then dump it out to a CSV file. As I mentioned earlier, this is just the start of a small project I&rsquo;ll be using to study for the DEVCOR exam - so this will be evolving into a web service later on.</p>
<p>The full script can be found <a href="https://github.com/0x2142/example-scripts/tree/master/scrapli-and-genie-demo">here</a>. But I&rsquo;ve posted just a snippet of how we count the different interface characteristics:</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"># Count all Ethernet interfaces</span>
</span></span><span class="line"><span class="cl"><span class="n">interfaceStats</span><span class="p">[</span><span class="s1">&#39;total&#39;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Count admin-down interfaces</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="ow">not</span> <span class="n">sh_int_parsed</span><span class="p">[</span><span class="n">iface</span><span class="p">][</span><span class="s1">&#39;enabled&#39;</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="n">interfaceStats</span><span class="p">[</span><span class="s1">&#39;intdisabled&#39;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Count not connected interfaces</span>
</span></span><span class="line"><span class="cl"><span class="k">elif</span> <span class="n">sh_int_parsed</span><span class="p">[</span><span class="n">iface</span><span class="p">][</span><span class="s1">&#39;enabled&#39;</span><span class="p">]</span> <span class="ow">and</span> <span class="n">sh_int_parsed</span><span class="p">[</span><span class="n">iface</span><span class="p">][</span><span class="s1">&#39;oper_status&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;down&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">interfaceStats</span><span class="p">[</span><span class="s1">&#39;intdown&#39;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Count up / connected interfaces - Then collect current speeds</span>
</span></span><span class="line"><span class="cl"><span class="k">elif</span> <span class="n">sh_int_parsed</span><span class="p">[</span><span class="n">iface</span><span class="p">][</span><span class="s1">&#39;enabled&#39;</span><span class="p">]</span> <span class="ow">and</span> <span class="n">sh_int_parsed</span><span class="p">[</span><span class="n">iface</span><span class="p">][</span><span class="s1">&#39;connected&#39;</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="n">interfaceStats</span><span class="p">[</span><span class="s1">&#39;intup&#39;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="n">speed</span> <span class="o">=</span> <span class="n">sh_int_parsed</span><span class="p">[</span><span class="n">iface</span><span class="p">][</span><span class="s1">&#39;bandwidth&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">speed</span> <span class="o">==</span> <span class="mi">10_000</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">interfaceStats</span><span class="p">[</span><span class="s1">&#39;intop10m&#39;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">speed</span> <span class="o">==</span> <span class="mi">100_000</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">interfaceStats</span><span class="p">[</span><span class="s1">&#39;intop100m&#39;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">speed</span> <span class="o">==</span> <span class="mi">1_000_000</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">interfaceStats</span><span class="p">[</span><span class="s1">&#39;intop1g&#39;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">speed</span> <span class="o">==</span> <span class="mi">10_000_000</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">interfaceStats</span><span class="p">[</span><span class="s1">&#39;intop10g&#39;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Count number of interfaces by media type</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">media</span> <span class="o">=</span> <span class="n">sh_int_parsed</span><span class="p">[</span><span class="n">iface</span><span class="p">][</span><span class="s1">&#39;media_type&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="s1">&#39;1000BaseTX&#39;</span> <span class="ow">in</span> <span class="n">media</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">interfaceStats</span><span class="p">[</span><span class="s1">&#39;intmedcop&#39;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</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">interfaceStats</span><span class="p">[</span><span class="s1">&#39;intmedsfp&#39;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</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">interfaceStats</span><span class="p">[</span><span class="s1">&#39;intmedsfp&#39;</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span></code></pre></div><p>Once all that runs, we create a CSV file to dump all of the data to.</p>
<p>For example, running the complete script against my lab Catalyst 9200 switch would output the following:</p>
<table>
  <thead>
      <tr>
          <th>Hostname</th>
          <th>Model</th>
          <th>Serial Number</th>
          <th>Software Version</th>
          <th>Total Interfaces</th>
          <th>Interfaces UP (Total)</th>
          <th>Interfaces DOWN (Total)</th>
          <th>Interfaces Disabled</th>
          <th>Interface Operational Speed (10M)</th>
          <th>Interface Operational Speed (100M)</th>
          <th>Interface Operational Speed (1G)</th>
          <th>Interface Operational Speed (10G)</th>
          <th>Interface Media (Copper)</th>
          <th>Interface Media (SFP)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SW-C9200</td>
          <td>C9200L-24T-4X</td>
          <td>XX########X</td>
          <td>16.9.4</td>
          <td>28</td>
          <td>7</td>
          <td>8</td>
          <td>13</td>
          <td>0</td>
          <td>1</td>
          <td>6</td>
          <td>0</td>
          <td>24</td>
          <td>4</td>
      </tr>
  </tbody>
</table>
<p>And that&rsquo;s it! Using the combination of scrapli &amp; genie made this script very quick and easy to write - which means now I can focus more time on the next steps.</p>
<hr>
<p>As I&rsquo;ve mentioned a few times, this is hopefully the start of a short series of blog posts &amp; videos as I wrap up studying for the DEVCOR exam.</p>
<p>If you found this content helpful - or you&rsquo;re interested in the future content - please check back soon! Or consider subscribing to my <a href="https://www.youtube.com/channel/UCTY24K3UQrc6xHFcLfGg1hg">YouTube</a> channel, where new content will be coming shortly.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Meraki MG - Setting up Meraki&#39;s New Cellular Gateway</title>
      <link>https://0x2142.com/meraki-mg-setting-up-merakis-new-lte-gateway/</link>
      <pubDate>Thu, 20 Aug 2020 21:17:23 +0000</pubDate>
      <guid>https://0x2142.com/meraki-mg-setting-up-merakis-new-lte-gateway/</guid>
      <description>I recently got a Meraki MG21 LTE gateway. Let&amp;rsquo;s set it up!</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/Zb5KE8_OFxQ?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>So if you&rsquo;ve read some of my recent posts - you may have seen that I purchased a <a href="/how-to-setting-up-google-fi-on-a-netgear-lte-modem/">NetGear LB 1121 LTE Cellular modem</a> to use for home internet backup.</p>
<p>Well - I decided to upgrade!</p>
<p>After using the NetGear modem for a while, I started having some issues where it would disconnect from the cellular connection intermittently. Since it&rsquo;s not necessarily intended for the purpose I&rsquo;m using it for, there wasn&rsquo;t any good way to set up monitoring for it either.</p>
<p>So I opted to upgrade to a Meraki MG21. This is one of the latest additions to the Meraki family of network devices &amp; is available with internal (MG21) and external (MG21E) antenna.</p>
<p>I was pretty excited to get one, since Meraki tends to have decent analytics &amp; configuration - and they make everything so easy!</p>
<p>We&rsquo;ll walk through the setup of the MG below - but if you&rsquo;re interested in seeing what the device looks like as well, definitely check out the video above!</p>
<hr>
<h2 id="mg-setup---changing-the-apn">MG Setup - Changing the APN</h2>
<p>Okay - so after I got the SIM card inserted into the MG, the first configuration step is making sure we have the correct APN configured. As a reminder, I&rsquo;m using Google Fi as my cellular provider.</p>
<p>This change will need to be done <strong>on the local web management interface</strong> - not from Meraki Dashboard.</p>
<p>When the MG powers on, by default it will hand out DHCP addresses to any device connected to port 1. At the time of writing, these addresses were in the 192.168.5.x range.</p>
<p>Connecting a PC directly to the MG port, we should be able to reach the local management web page - either by typing in the IP address into our web browser, or using mg.meraki.com:</p>
<p><img alt="blog-01" loading="lazy" src="/content/images/2020/08/blog-01.PNG#center"></p>
<p>In my case, I could see that the MG had auto-detected my Carrier as Google Fi. However, it still had the incorrect APN.</p>
<p>Using the <strong>Configure</strong> tab, we can change that setting. You&rsquo;ll be prompted for a username &amp; password. By default, the username will be the serial number of the device (including dashes) with a blank password.</p>
<p><img alt="blog-02" loading="lazy" src="/content/images/2020/08/blog-02.PNG#center"></p>
<p>As shown in the screenshot above, we have a handful of options - though we&rsquo;ll only care about APN.</p>
<p>First - change the <strong>Cellular Override</strong> option to <strong>Override SIM Settings</strong>.
Then type in your APN. In my case, it&rsquo;s <strong>h2g2</strong> for Google Fi.
Lastly, hit save at the bottom (just outside the view in the screenshot above).</p>
<p>With any luck, the modem will connect and you&rsquo;ll see something like this:</p>
<p><img alt="blog-03" loading="lazy" src="/content/images/2020/08/blog-03.png#center"></p>
<p>The modem connects, gets an IP from the provider, and is able to validate connectivity to both the internet &amp; Meraki Cloud.</p>
<p>When I originally set up my MG, I did run into some issues with this. My MG connected to the internet successfully, but said it couldn&rsquo;t reach the Meraki Cloud. Not sure what caused it, but it shortly resolved itself within a few minutes. Just gotta be patient sometimes, I suppose!</p>
<h2 id="configuring-the-mg-in-meraki-dashboard">Configuring the MG in Meraki Dashboard</h2>
<p><em>Note: I won&rsquo;t get into how to claim your device in dashboard or how to attach it to a network. If you need help, please check out the video above where I did show how to accomplish these steps</em></p>
<p>Okay! Now that our MG is configured for the correct cell network, we can log into the Meraki Dashboard and begin configuring it.</p>
<p>After we&rsquo;ve added the MG to our network, we&rsquo;ll see a new <strong>Cellular Gateway</strong> menu:</p>
<p><img alt="blog-04" loading="lazy" src="/content/images/2020/08/blog-04.png#center"></p>
<p>We&rsquo;ll start first by going over to <strong>Configure &gt; Settings</strong></p>
<p>First section we&rsquo;ll see is for <strong>Addressing &amp; NAT</strong>:</p>
<p><img alt="blog-05" loading="lazy" src="/content/images/2020/08/blog-05.png#center"></p>
<p>As of today, the MG doesn&rsquo;t support any form of direct internet pass-through. Instead, our only option is <strong>routed</strong> mode - where the MG will hold the IP provided by our Carrier &amp; NAT any requests from the devices behind it.</p>
<p>We can change the DHCP subnet configuration here, which will affect what IP addresses are handed to clients behind the MG. In my case, I&rsquo;m connecting this directly to a firewall as a secondary internet uplink - so the addressing &amp; subnet doesn&rsquo;t matter as much. By default, the MG will always consume the first available address as it&rsquo;s own.</p>
<p>Next, we have a section for <strong>DHCP &amp; subnets</strong>:</p>
<p><img alt="blog-06" loading="lazy" src="/content/images/2020/08/blog-06.png#center"></p>
<p>Here we can change our DHCP lease time, and what DNS servers are provided to our clients. The DNS setting does have pre-defined options for Umbrella DNS, Google DNS, or using whatever the upstream carrier provides. You&rsquo;re also welcome to manually specify which DNS servers to use.</p>
<p>We can also configure reserved &amp; fixed IP addresses here.</p>
<p>Reserved IP ranges are IP addresses that we don&rsquo;t want the MG to provide via DHCP. So if we had any statically configured IP addresses, we could reserve them here.</p>
<p>Fixed IP addresses are for any client that needs a DHCP address, but we want that IP assignment to be permanent. We&rsquo;ll enter the client name &amp; MAC Address here, as well as the IP we want assigned to that device. In my case, I went ahead and inserted my firewall MAC address - and I&rsquo;ll just allow the firewall to get its IP via DHCP from the MG.</p>
<p>By default, the MG will block <strong>all</strong> inbound traffic from the cellular network. If we need to allow any traffic inbound, we can change the <strong>Port Forwarding</strong> settings:</p>
<p><img alt="blog-07" loading="lazy" src="/content/images/2020/08/blog-07.png#center"></p>
<p>This allows for a light configuration of an inbound NAT. Right now, I probably won&rsquo;t be using this. However, I may permit VPN access into my network via the MG at a later date.</p>
<p>If I needed to add anything here, the MG allows us to translate an external / public IP &amp; port to any internal IP / port combination. It appears we can even add a IP filter to permit only trusted source addresses.</p>
<p>Lastly - We can configure settings for <strong>Traffic Shaping</strong>:</p>
<p><img alt="blog-08" loading="lazy" src="/content/images/2020/08/blog-08.PNG#center"></p>
<p>In this section, we can throttle our cellular throughput &amp; configure uplink monitoring.</p>
<p>By default, the cell bandwidth is set to unlimited - but we can drop this down if needed. In my case, I am not using an unlimited cell data plan - so I will throttle cellular speeds to preserve data &amp; reduce charges.</p>
<p>In addition, we can configure one or more IP addresses to check uplink connectivity. These addresses will be used to collect loss &amp; latency data via the cellular connection. The MG monitoring dashboard will collect &amp; graph this data for easy insight into the performance metrics.</p>
<p><strong>Note: As a word of warning, these uplink monitors are constantly sending ICMP/ping requests. If you have a limited amount of cellular data, this may consume more data than you would like. In my testing, using only one IP for uplink monitoring consumed about 70-100M per day. More on this below&hellip;</strong></p>
<h2 id="monitoring-the-mg">Monitoring the MG</h2>
<p>Now we get to the good stuff! The primary reason I opted to buy an MG was for monitoring &amp; analytics.</p>
<p>Back on the dashboard, if we use the lefthand menu - we&rsquo;ll go over to <strong>Cellular Gateway &gt; Monitor &gt; Cellular Gatways</strong>. Then select our MG out of the list.</p>
<p>The primary summary page isn&rsquo;t too exciting:</p>
<p><img alt="blog-09" loading="lazy" src="/content/images/2020/08/blog-09.png#center"></p>
<p>The MG does have two gigabit ethernet ports - and we&rsquo;ll see the status here.</p>
<p>We&rsquo;ll also see the connectivity history to the Meraki cloud - which in my case is nearly 100%. Seems like one <em>very</em> minor blip just after 4am.</p>
<p>We can also see the current network utilization on the MG. This is great to have - though my current utilization is pretty low&hellip; (I am using this as a backup modem, after all).</p>
<p>On the left side of the page, we&rsquo;ll see some of the usual info we expect from a Meraki device. Current IP, location, Serial number, and IMEI. Just below the view of the screenshot, there is also an indicator for firmware version.</p>
<p>Onto the <strong>Uplink</strong> tab! Let&rsquo;s see what we have:</p>
<p><img alt="blog-10" loading="lazy" src="/content/images/2020/08/blog-10.png#center"></p>
<p>First we&rsquo;ll see the <strong>Configuration</strong> section. This just gives us a quick view into what settings the MG currently has.</p>
<p>We&rsquo;ll see the current IP info provided by our carrier, and also some statistics on our cellular connection.</p>
<p>Just below that info, we&rsquo;ll see our cellular graphs:</p>
<p><img alt="blog-11" loading="lazy" src="/content/images/2020/08/blog-11.png"></p>
<p>This is what I wanted! It&rsquo;s great to see a quick view into what our active uplink traffic is - as well as look back historically at what our LTE signal quality has been.</p>
<p>Not pictured here - but there is also a section of graphs on this page for our uplink monitor. This is where we can see our current &amp; historical loss &amp; latency stats for the cellular connection. After a few days of use - I disabled the uplink monitor due to the amount of data the feature consumes.</p>
<p>Finally, we also have the <strong>DHCP</strong> tab:</p>
<p><img alt="blog-12" loading="lazy" src="/content/images/2020/08/blog-12.PNG#center"></p>
<p>This will show our current DHCP subnet &amp; any clients that have been provided an address. In my case, there isn&rsquo;t any current leases here - because my firewall has a fixed IP.</p>
<h2 id="performance--considerations">Performance &amp;&amp; Considerations</h2>
<p>I&rsquo;ve had the MG running for about a week now, and wanted to provide some things to think about.</p>
<p>First - How does the modem perform? Well, the first day I had it set up - I was able to get ~150M download speeds using the MG&rsquo;s built-in speed test utility:</p>
<p><img alt="blog-13" loading="lazy" src="/content/images/2020/08/blog-13.PNG#center"></p>
<p>That being said - I&rsquo;m lucky if I get 30-50M on an average day. I might have just gotten lucky that day with some light cellular utilization in my area. Overall though, I&rsquo;m pleased with the speeds I get - they&rsquo;ll certainly fit my needs.</p>
<p>For the few days that I had the uplink monitoring running, I saw good results. Usually 0% packet loss, with a rare spike of 5-10%. Latency was a little less reliable, but usually bounced between 50-150ms. This was also much less than the NetGear modem I had been using, which averaged 200-250ms.</p>
<p>Speaking of uplink tests! Let&rsquo;s talk about data usage&hellip;.</p>
<p>By default, the MG communicates intermittently with the Meraki cloud - which consumes some data. By my measurement, this is usually less than 10Mb/day. No problem here.</p>
<p>The uplink tests, on the other hand, <strong>do</strong> consume a bit of data. I&rsquo;m not sure what frequency these run on, but it&rsquo;s fairly often. Even with one uplink monitor to 8.8.8.8 configured, I was seeing data usage of 70-100Mb a day.</p>
<p>While seeing those metrics is valuable to me, it&rsquo;s also not worth the data charges. If I was using a SIM card with an unlimited data plan - no doubt I would keep this feature enabled. However, since I am paying for the cell data used - I opted to disable this feature.</p>
<p>The MG does still perform it&rsquo;s check-ins to the Meraki Cloud - so you&rsquo;ll have availability statistics &amp; monitoring&hellip; But disabling the uplink monitor means you&rsquo;ll lose the granular data on loss &amp; latency.</p>
<p>Lastly, and another word of warning, when you&rsquo;re actively viewing the MG monitoring page - this <strong>also consumes additional data</strong>. To demonstrate - I&rsquo;ll post a snippet of the screenshot from earlier:</p>
<p><img alt="blog-14" loading="lazy" src="/content/images/2020/08/blog-14.png#center"></p>
<p>If you notice, all the way on the far left there was barely any activity. However, once I loaded the MG monitoring page - you begin to see minor spikes in data usage as the Meraki Cloud starts actively polling the MG for data.</p>
<p>In my experience so far, this isn&rsquo;t a ton of data. I&rsquo;ve checked in to see how the MG has been performing a few times this week, and each time has totaled around 10-15Mb of data usage.</p>
<p>To sum up - I&rsquo;m cheap and want to avoid excess data usage. Just wanted to provide some of that info as something to be aware of.</p>
<h2 id="final-thoughts">Final Thoughts</h2>
<p>I&rsquo;m only a week in, but pretty pleased with the MG&rsquo;s performance. It&rsquo;s maintained a very solid &amp; stable connection compared to the NetGear modem it replaced. The device is intended to provide LTE connectivity or backup service for business networks, so I would certainly hope it would meet my home needs :)</p>
<p>Outside of that, I do wish there was a little better documentation &amp; clarity from the Meraki team on data usage. Right now their documentation only mentions the 6-8Mb of usage due to backend data to/from the Meraki Cloud:</p>
<p><img alt="blog-15" loading="lazy" src="/content/images/2020/08/blog-15.PNG#center"></p>
<p>I would be happy to see additional settings on the uplink monitor to allow me to choose the polling frequency. I feel like throttling down the amount of requests could reduce data to a point where I would be comfortable re-enabling that feature.</p>
<p><del>Oh - and currently there are no native email alerts for the MG. So if the MG goes offline, etc&hellip; there is no alerting from the Meraki Dashboard. This kinda sucks. I&rsquo;m sure this is coming soon, but for the time being I&rsquo;m inclined to write my own monitor using the Dashboard APIs.</del> (see note below!)</p>
<p>Overall, I&rsquo;m happy with the device. Definitely looking forward to future feature &amp; firmware updates to see where the Meraki team takes this platform!</p>
<hr>
<p><em>Update 08/28/2020 - Looks like in the week since I posted this, Meraki added alerting for the MG! Now you can be notified if the cell gateway goes offline:</em></p>
<p><img alt="blog-16" loading="lazy" src="/content/images/2020/08/blog-16.PNG#center"></p>
]]></content:encoded>
    </item>
    <item>
      <title>Cisco Firepower - Automating Cellular Failover</title>
      <link>https://0x2142.com/cisco-firepower-automating-cellular-failover/</link>
      <pubDate>Fri, 17 Jul 2020 20:41:09 +0000</pubDate>
      <guid>https://0x2142.com/cisco-firepower-automating-cellular-failover/</guid>
      <description>How I automated internet uplink monitoring &amp;amp; route injection on a Cisco Firepower Firewall</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/Mf5zIt9HFxk?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>In a <a href="/how-to-setting-up-google-fi-on-a-netgear-lte-modem/">recent post</a>, I walked through setting up a Netgear LTE cellular modem with Google Fi. Might seem random - but I had a plan for this!</p>
<p>In trying to ensure I keep a stable internet connection for work, I eventually led myself down the path of buying a cellular modem. That was part one. The next step was being able to somewhat-intelligently monitor my home internet connection, and then fail over to the cell modem when connectivity was poor (think lazy SDWAN).</p>
<p>At first I figured this would be easy&hellip;. but of course nothing is :)</p>
<p>I currently have a Cisco FirePower 1010 appliance that I&rsquo;m using for a home firewall. Unfortunately, while this thing does support IP SLA - It wouldn&rsquo;t quite accomplish what I wanted to do. And to be fair, this box is intended to be a <em>security</em> appliance - not SDWAN.</p>
<p>Anyways - I talked myself into just writing some Python automation to monitor my home internet, and dynamically inject/remove static routing entries. I needed a new project anyways</p>
<p><em>For those of you wanting to jump straight to the code, the GitHub repo is <a href="https://github.com/0x2142/fpwr-route-failover">here</a></em></p>
<hr>
<h2 id="part-1---monitoring-packet-loss--latency">Part 1 - Monitoring Packet Loss &amp; Latency</h2>
<p>So first thing - I regularly experience both complete internet outages (always at the worst times), as well as very high latency. Packet loss seems to be less common with my ISP - but hey, it happens sometimes too.</p>
<p>I opted to use the <a href="https://pypi.org/project/pythonping/">pythonping</a> module as an easy way to collect some of the info I needed.</p>
<p>After importing the module, it&rsquo;s as easy as specifying a destination, packet size, and number of ICMP messages to send:</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">pythonping</span> <span class="kn">import</span> <span class="n">ping</span>
</span></span><span class="line"><span class="cl"><span class="n">result</span> <span class="o">=</span> <span class="n">ping</span><span class="p">(</span><span class="s1">&#39;8.8.8.8&#39;</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">count</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">verbose</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span></code></pre></div><p>To simplify at least one part of the operation, pythonping has a built in function to return the average round trip:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">&gt;&gt;&gt;</span> <span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">rtt_avg_ms</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="mf">74.23</span>
</span></span></code></pre></div><p>The actual contents of our ping results are going to be in the following format:
<code>Reply from 8.8.8.8, 10 bytes in 43.82ms</code></p>
<p>So a quick way for me to figure out packet loss was to simply search for the &ldquo;Reply from&rdquo; text in each 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="n">lost</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">packet</span> <span class="ow">in</span> <span class="n">result</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="s2">&#34;Reply from&#34;</span> <span class="ow">in</span> <span class="nb">str</span><span class="p">(</span><span class="n">packet</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">pass</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">lost</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span></code></pre></div><p>Easy enough - and we can use that to calculate the amount of packet loss against the 10 total packets sent:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">lossperc = 100 * lost / 10
</span></span></code></pre></div><p>Once all that is figured out, we can use a simple expression to determine whether or not the loss or latency thresholds have been exceeded:</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">if</span> <span class="n">response</span> <span class="o">&gt;=</span> <span class="n">MAX_LATENCY</span> <span class="ow">or</span> <span class="n">loss</span> <span class="o">&gt;=</span> <span class="n">MAX_LOSS</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Loss/Latency violate thresholds.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">True</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="nb">print</span><span class="p">(</span><span class="s2">&#34;Loss/Latency within thresholds.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">False</span>
</span></span></code></pre></div><p>The code I wrote then takes one of two paths.</p>
<ul>
<li>If the loss and latency is ABOVE our thresholds, call to the FirePower module to inject a static default route over the LTE modem</li>
<li>If the loss and latency is BELOW our thresholds, call to the FirePower module to delete the static default route - which returns traffic to the primary internet</li>
</ul>
<p>These functions within the FirePower module were written so that each change will only be made once. For example, if the script checks and finds that the loss/latency is bad, but the static route towards the LTE modem already exists - then no additional changes are made.</p>
<p>So next - Let&rsquo;s take a look at the FirePower module.</p>
<h2 id="part-2---authenticating-to-fdm--initial-checks">Part 2 - Authenticating to FDM &amp; Initial Checks</h2>
<p>This was my first time getting into the FirePower FDM APIs - but they ended up being fairly straightforward to use.</p>
<p>Turns out that FDM has built-in API documentation, which is extremely helpful. This can be found at <code>https://&lt;FDM IP&gt;/#/api-explorer</code></p>
<p><img alt="firepower-api-explorer" loading="lazy" src="/content/images/2020/07/firepower-api-explorer.PNG#center"></p>
<p>Okay - So first thing&rsquo;s first. Authenticating to the firewall:</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">oauth_data</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;grant_type&#34;</span><span class="p">:</span> <span class="s2">&#34;password&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;username&#34;</span><span class="p">:</span> <span class="s2">&#34;admin&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;password&#34;</span><span class="p">:</span> <span class="s2">&#34;Cisco1234!&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">headers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;Content-Type&#34;</span><span class="p">:</span> <span class="s2">&#34;application/json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;Accept&#34;</span><span class="p">:</span> <span class="s2">&#34;application/json&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">baseurl</span> <span class="o">=</span> <span class="s2">&#34;https://&#34;</span> <span class="o">+</span> <span class="n">FDM</span> <span class="o">+</span> <span class="s2">&#34;/api/fdm/latest&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">authurl</span> <span class="o">=</span> <span class="n">baseurl</span> <span class="o">+</span> <span class="s2">&#34;/fdm/token&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Posting AUTH request to FDM&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># POST request to FDM with headers / oauth data</span>
</span></span><span class="line"><span class="cl"><span class="n">resp</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">s</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">authurl</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                   <span class="n">data</span><span class="o">=</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">oauth_data</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                   <span class="n">verify</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># If success - only pull out &amp; return the auth token</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Auth success - got token.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">text</span><span class="p">)[</span><span class="s1">&#39;access_token&#39;</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="nb">print</span><span class="p">(</span><span class="s2">&#34;Authentication Failed.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span></code></pre></div><p>In the code above - we take the headers and some basic authentication info (username, password, grant type) and send it in an HTTP POST request to <code>https://&lt;FDM IP&gt;/api/fdm/latest/fdm/token</code></p>
<p>This returns an authentication token, which we&rsquo;ll need to include in any further HTTP request to the firewall. This can be done by sending a new set of headers in the future:</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">headers</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;Content-Type&#34;</span><span class="p">:</span> <span class="s2">&#34;application/json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;Accept&#34;</span><span class="p">:</span> <span class="s2">&#34;application/json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;Authorization&#34;</span><span class="p">:</span> <span class="s2">&#34;Bearer &#34;</span> <span class="o">+</span> <span class="bp">self</span><span class="o">.</span><span class="n">token</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Next we need to figure out which routing table to insert the route into. Since I am only using the default routing table, the script will just grab the UID for the default global routing table:</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">vr_url</span> <span class="o">=</span> <span class="n">baseurl</span> <span class="o">+</span> <span class="s2">&#34;/devices/default/routing/virtualrouters&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">routing_table</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">vr_url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
</span></span></code></pre></div><p>I mentioned earlier that the script will not make any changes if the firewall is already in the desired state. So we will take time now to check what state the firewall is in, before attempting to make any changes.</p>
<p>This is accomplished by making our first primary action scanning the routing table to see if our route already exists:</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">route_url</span> <span class="o">=</span> <span class="n">baseurl</span> <span class="o">+</span> <span class="s2">&#34;/devices/default/routing/virtualrouters/&#34;</span> <span class="o">+</span> \
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">globalVR</span> <span class="o">+</span> <span class="s2">&#34;/staticrouteentries&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">route_data</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">route_url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Convert returned JSON object</span>
</span></span><span class="line"><span class="cl"><span class="n">current_routes</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">route_data</span><span class="p">)[</span><span class="s1">&#39;items&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Run through each route returned and see if it matches the one we&#39;re looking for</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">route</span> <span class="ow">in</span> <span class="n">current_routes</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># For each route, we need to manually go look up the uid of our gateway &amp; network objects</span>
</span></span><span class="line"><span class="cl">    <span class="n">gateway</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getNetworkObject</span><span class="p">(</span><span class="n">route</span><span class="p">[</span><span class="s1">&#39;gateway&#39;</span><span class="p">][</span><span class="s1">&#39;id&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="n">dest_network</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">getNetworkObject</span><span class="p">(</span><span class="n">route</span><span class="p">[</span><span class="s1">&#39;networks&#39;</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s1">&#39;id&#39;</span><span class="p">])</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Match based on route prefix &amp; upstream next hop gateway</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">gateway</span> <span class="o">==</span> <span class="n">GATEWAY</span> <span class="ow">and</span> <span class="n">dest_network</span> <span class="o">==</span> <span class="n">ROUTE</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Found route to </span><span class="si">%s</span><span class="s2"> via </span><span class="si">%s</span><span class="s2">&#34;</span> <span class="o">%</span> <span class="p">(</span><span class="n">dest_network</span><span class="p">,</span> <span class="n">gateway</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">route</span>
</span></span></code></pre></div><p>Depending on whether we were looking to add or remove a route, the script may proceed with doing so - or just quit if the action has already been taken previously.</p>
<h2 id="part-3---failover-add-static-route">Part 3 - Failover (Add Static Route)</h2>
<p>Assuming that we need to <strong>Add</strong> a route, we need to sent a POST request to <code>https://&lt;FDM IP&gt;/api/fdm/latest/devices/default/routing/virtualrouters/&lt;Virtual Router ID&gt;/staticrouteentries</code> with some required information (like subnet info, next-hop, etc)</p>
<p>Unfortunately, we can&rsquo;t just send a POST with the target subnet &amp; gateway. Those need to be created as network objects on the FirePower box beforehand. For each network object, we need to build a quick JSON object with the required info:</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">host_data</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="n">host_data</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">name</span>
</span></span><span class="line"><span class="cl"><span class="n">host_data</span><span class="p">[</span><span class="s1">&#39;description&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;Created by ISP Failover automation&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">host_data</span><span class="p">[</span><span class="s1">&#39;subType&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">subtype</span>
</span></span><span class="line"><span class="cl"><span class="n">host_data</span><span class="p">[</span><span class="s1">&#39;value&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">address</span>
</span></span><span class="line"><span class="cl"><span class="n">host_data</span><span class="p">[</span><span class="s1">&#39;type&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;networkobject&#34;</span>
</span></span></code></pre></div><p>Then we can send a POST to the FDM API to create the object with our supplied parameters:</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">posturl</span> <span class="o">=</span> <span class="n">baseurl</span> <span class="o">+</span> <span class="s2">&#34;/object/networks&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">posturl</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">host_data</span><span class="p">)</span>
</span></span></code></pre></div><p>Once we have those objects created, we can compile all of that info into a JSON object with all the components needed to create a static route entry:</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">routeobject</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;route_BACKUP&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;description&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;Created by ISP Failover automation&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;iface&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;iface&#39;</span><span class="p">][</span><span class="s1">&#39;id&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">iface_ID</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;iface&#39;</span><span class="p">][</span><span class="s1">&#39;type&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;physicalinterface&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;iface&#39;</span><span class="p">][</span><span class="s1">&#39;name&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">iface_name</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;networks&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="p">[{}]</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;networks&#39;</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;networks&#39;</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s1">&#39;id&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">network_ID</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;networks&#39;</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s1">&#39;type&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;networkobject&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;networks&#39;</span><span class="p">][</span><span class="mi">0</span><span class="p">][</span><span class="s1">&#39;name&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">network_name</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;gateway&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;gateway&#39;</span><span class="p">][</span><span class="s1">&#39;id&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">gateway_ID</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;gateway&#39;</span><span class="p">][</span><span class="s1">&#39;type&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;networkobject&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;gateway&#39;</span><span class="p">][</span><span class="s1">&#39;name&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">gateway_name</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;metricValue&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;ipType&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;IPv4&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">routeobject</span><span class="p">[</span><span class="s1">&#39;type&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;staticrouteentry&#34;</span>
</span></span></code></pre></div><p>Then pass all that into a POST request to create the route object:</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">add_url</span> <span class="o">=</span> <span class="n">baseurl</span> <span class="o">+</span> <span class="s2">&#34;/devices/default/routing/virtualrouters/&#34;</span> <span class="o">+</span> <span class="bp">self</span><span class="o">.</span><span class="n">globalVR</span> <span class="o">+</span> <span class="s2">&#34;/staticrouteentries</span>
</span></span><span class="line"><span class="cl"><span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">add_url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">routeobject</span><span class="p">)</span>
</span></span></code></pre></div><p>Success? Well not quite yet.</p>
<p>FirePower requires all changes to be deployed (or applied) to the system before they take effect.</p>
<p>So next we need to initiate a deployment task - and periodically check on it. Deployments can take a <em>while</em> to complete, depending on number of changes, processing power of the appliance, etc.</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"># First send a POST to the deployment endpoint:</span>
</span></span><span class="line"><span class="cl"><span class="n">deploy_url</span> <span class="o">=</span> <span class="n">baseurl</span> <span class="o">+</span> <span class="s2">&#34;/operational/deploy&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">deploy_response</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">deploy_url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Grab the deployment task UID:</span>
</span></span><span class="line"><span class="cl"><span class="n">deploymentID</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">deploy_response</span><span class="o">.</span><span class="n">text</span><span class="p">)[</span><span class="s1">&#39;id&#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"># Loop while deployment is running:</span>
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="n">deployed</span> <span class="ow">is</span> <span class="kc">False</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Sleep for a few seconds:</span>
</span></span><span class="line"><span class="cl">    <span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Get list of all deployment tasks:</span>
</span></span><span class="line"><span class="cl">    <span class="n">tasklist</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">deploy_url</span><span class="p">)</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">task</span> <span class="ow">in</span> <span class="n">taskList</span><span class="p">[</span><span class="s1">&#39;items&#39;</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">task</span><span class="p">[</span><span class="s1">&#39;id&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="n">deploymentID</span> <span class="ow">and</span> <span class="n">task</span><span class="p">[</span><span class="s1">&#39;state&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="s1">&#39;DEPLOYED&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Deployment status is: &#34;</span> <span class="o">+</span> <span class="n">task</span><span class="p">[</span><span class="s1">&#39;state&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">            <span class="n">deployed</span> <span class="o">=</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="n">task</span><span class="p">[</span><span class="s1">&#39;id&#39;</span><span class="p">]</span> <span class="o">==</span> <span class="n">deploymentID</span> <span class="ow">and</span> <span class="n">task</span><span class="p">[</span><span class="s1">&#39;state&#39;</span><span class="p">]</span> <span class="o">!=</span> <span class="s1">&#39;DEPLOYED&#39;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># If changes not yet deployed, check again momentarily</span>
</span></span><span class="line"><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Deployment status is: &#34;</span> <span class="o">+</span> <span class="n">task</span><span class="p">[</span><span class="s1">&#39;state&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">            <span class="n">deployed</span> <span class="o">=</span> <span class="kc">False</span>
</span></span></code></pre></div><p>So in the above block, we POST a request to deploy changes. The response of that POST request contains our deployment ID, which we keep to check the status later.</p>
<p>Then the script waits a few seconds, and pulls a list of all deployment tasks. Search through that list for our deployment ID - and check to see if it has been completed yet. If not, wait and run the loop again.</p>
<p>And that&rsquo;s it! Once we get confirmation that our changes have been deployed - we&rsquo;ve now routed traffic over the secondary connection (an LTE modem, in my case).</p>
<h2 id="part-4---fail-back-delete-static-route">Part 4 - Fail-Back (Delete Static Route)</h2>
<p>Okay - this section will be fairly quick. We&rsquo;ve already looked at authenticating, checking if our route already exists, and how to add a new route. So the only thing remaining is removing our route if we wanted to migrate traffic back to the primary connection.</p>
<p>Same logic applies as earlier. If our path monitoring script runs and finds that loss &amp; latency are within the thresholds we set, then we make a call to the firepower module. First we check to ensure there <em>is</em> a route for us to delete, and if so - proceed with deleting it.</p>
<p>Adding a route is a little more work, since we may need to create network objects. However, when we delete the route - we&rsquo;ll just leave those objects on the FirePower box. They&rsquo;ll be there for the next time we need them (which also speeds up deployment times).</p>
<p>To get started, we just need the UID for the route we want to delete.</p>
<p>Remember that code I showed above, where we check to see if the static route exists? And match on our intended subnet / gateway pair? Well, I just made that into a function that will return the UID of the route if it finds it. Easy enough!</p>
<p>Next, we just send an HTTP DELETE to the static routing endpoint - and reference that UID:</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">del_url</span> <span class="o">=</span> <span class="n">baseurl</span> <span class="o">+</span> <span class="s2">&#34;/devices/default/routing/virtualrouters/&#34;</span> <span class="o">+</span> \
</span></span><span class="line"><span class="cl">          <span class="bp">self</span><span class="o">.</span><span class="n">globalVR</span> <span class="o">+</span> <span class="s2">&#34;/staticrouteentries/&#34;</span> <span class="o">+</span> <span class="n">route</span><span class="p">[</span><span class="s1">&#39;id&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">requests</span><span class="o">.</span><span class="n">delete</span><span class="p">(</span><span class="n">del_url</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">)</span>
</span></span></code></pre></div><p>Once that succeeds - we use the same logic as earlier to deploy the changes.</p>
<p>After that, our route is removed &amp; traffic should be flowing over our primary connection again.</p>
<hr>
<p>If it helps anyone - I also threw together a quick diagram to explain the flow of operations here:
<img alt="flow-diagram" loading="lazy" src="/content/images/2020/07/flow-diagram.jpg#center"></p>
<hr>
<p>Well - that&rsquo;s it. This was a fun side project to work on over the past week or two. I hadn&rsquo;t spent much time with the FirePower/FDM APIs just yet. While there was a bit of work in creating all the necessary network objects, the overall process was fairly simple.</p>
<p>It helped tremendously that the FDM API explorer exists, and is available on-box. This utility allows you to see all the available API calls, what parameters they require, and even run test calls from the web UI. This greatly reduced the time needed to figure this out.</p>
<p>Hope this was interesting. If you would like to see the whole project - check out the repo on my GitHub page.</p>
]]></content:encoded>
    </item>
    <item>
      <title>How To: Convert Catalyst 9100 AP to Embedded WLC</title>
      <link>https://0x2142.com/how-to-convert-catalyst-9100-ap-to-embedded-wlc/</link>
      <pubDate>Tue, 05 May 2020 16:19:35 +0000</pubDate>
      <guid>https://0x2142.com/how-to-convert-catalyst-9100-ap-to-embedded-wlc/</guid>
      <description>A tutorial for converting a Cisco 9100 series wireless access point to host an embedded controller</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/NBt370eiQ3I?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>Hey there! First thing&rsquo;s first - Hope all is well with everyone. The past few months have seen all sorts of craziness going on in the world. I&rsquo;ve been lucky in the sense that my current job role was already well set up to make an easy transition to working from home all the time. That being said, I know it hasn&rsquo;t been easy for everyone - and I know quite a few people who have lost their jobs, etc.</p>
<p>Second thing! As I&rsquo;m sure you may have already seen above - I opted to spend some of my new-found free time trying out a new format. I&rsquo;ve had a couple of ideas in the past for making short videos, but never forced myself to sit down and give it a shot. So here we are - after about a month of on-and-off work - I finally have something to share. Please give it a look if you&rsquo;re interested, and I would appreciate any comments &amp; feedback. I still have a few other ideas - so if this one goes well I may pursue producing a few more of these.</p>
<p>Okay - Now onto the real content!</p>
<hr>
<p>A few months ago I had the chance to pick up a pair of Cisco Catalyst 9100 series access points from work. My home network has been running on Ubiquiti APs for years - but they&rsquo;re getting old and I desperately needed to replace them. So along comes the Catalyst 9100 APs, which are capable of supporting 802.11ax clients - and why not take the time to upgrade &amp; future proof? I needed some practice anyways, as I have a few customers at work that I think will be interested in these in the near future. I ended up with two 9120AXI APs.</p>
<p>So in the process of trying to dive on the opportunity to play with something new - I completely missed the fact that the 9100 APs have two different product SKUs. One which ships the AP pre-configured with the standard lightweight AP code that you might use if you had an external wireless LAN controller (WLC). And a second SKU that ships the AP with the Catalyst 9800 Embedded WLC (eWLC) software loaded. Of course, I grabbed the lightweight APs without checking.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Product SKUs (Example, using 9120AXI):
</span></span><span class="line"><span class="cl"> - Standard Lightweight AP: C9120AXI
</span></span><span class="line"><span class="cl"> - Pre-load with Embedded WLC: C9120AXI-EWC
</span></span></code></pre></div><p>I wrote in a previous blog about getting <a href="/how-to-catalyst-9800-mac-filtering/">MAC address filtering</a> set up on the Catalyst 9800 WLC. When I wrote that blog I was actually using the C9800-CL, which is a virtual machine version of the Catalyst 9800 controller software. I was originally excited to run the controller as a VM and not need a hardware appliance - but then got to thinking that maybe I should try and save VM resources as well. Which led me to looking at the 9800 eWLC.</p>
<hr>
<h2 id="finding-the-software-image">Finding the Software Image</h2>
<p>In order to convert an existing lightweight access point to one running the embedded WLC software - we first need to grab a copy of the software images from Cisco.com. Search for the model of your 9100 access point (in my case, the 9120AXI).</p>
<p>You&rsquo;ll see two options for Software Type - and it may not immediately be obvious - but we&rsquo;ll need to look under IOS XE Software to find the eWLC images:</p>
<p><img alt="image" loading="lazy" src="/content/images/2020/05/image.png#center"></p>
<p>Then we&rsquo;ll grab the EWC AP image bundle. In my case, I was waiting on a specific feature set that wasn&rsquo;t available until 17.x - so I downloaded the 17.2.1 software:</p>
<p><img alt="image" loading="lazy" src="/content/images/2020/05/image-1.png#center"></p>
<p>Okay - after we&rsquo;ve done that - let&rsquo;s drop everything onto a server/laptop/etc which has console connectivity to our AP and a running TFTP server.</p>
<p>Once you un-zip the contents of that AP image bundle, you&rsquo;ll notice there are quite a number of files. We&rsquo;ll only need two of them - one image to load onto the AP itself, and one that will get loaded for the WLC software container. Within the image bundle, there will be a <strong>readme.txt</strong> file that will tell you which image to use with your AP:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ap1g5 : AP1815, AP154x
</span></span><span class="line"><span class="cl">ap1g4 : AP180x, AP183x, AP185x
</span></span><span class="line"><span class="cl">ap1g7 : C9115, C9120
</span></span><span class="line"><span class="cl">ap1g6 : C9117
</span></span><span class="line"><span class="cl">ap1g6a : C9130
</span></span><span class="line"><span class="cl">ap3g3 : AP380x, AP280x, AP156x
</span></span></code></pre></div><p>So in my case, I would use the file named <strong>ap1g7</strong> since I had the 9120 APs. In addition, you should also see a file named <strong>C9800-AP-iosxe-wlc.bin</strong>which we&rsquo;ll need to load the controller.</p>
<p>What&rsquo;s with that second image? Well - the Catalyst 9100 APs include a feature called Application Hosting (also found on some of the Catalyst 9000 series switches). This is equivalent to running a Linux container or Docker container directly on the AP hardware. At time of writing, this is only available for the embedded WLC software. However,  there will be a future software update that will allow you to provision other software containers as well (according to the data sheet).</p>
<h2 id="converting-the-access-point">Converting the Access Point</h2>
<p>Now we can get started on the actual conversion process. If it&rsquo;s a new AP, we can just go ahead and boot it up with a console cable connected. If it&rsquo;s already running something, you&rsquo;ll likely want to factory reset the AP first. You can find detailed instructions <a href="https://www.cisco.com/c/en/us/support/docs/wireless/embedded-wireless-controller-on-catalyst-access-points/215303-embedded-wireless-controller-conversion.html#anc7">here</a>, but a quick summary - power off the AP, hold the <strong>mode</strong> button while plugging the AP back in, and keep the <strong>mode</strong> button held for at least 20-30 seconds. If you&rsquo;re logged into the console during this time - you&rsquo;ll actually see the AP counting how long you&rsquo;ve held the button for. Once that counter shows at least 20 seconds, release the <strong>mode</strong>button and allow the AP to reboot.</p>
<p>Once the AP is booted. The default login is <strong>Cisco</strong> / <strong>Cisco</strong>. The enable password is also <strong>Cisco</strong>. You may also want to quickly issue a <strong>show version</strong>, and take note of the current software version running on the AP (hint: we&rsquo;ll need that later).</p>
<p>Next - it&rsquo;s important to note that <em>after</em> we convert the AP - our default CLI will actually be the WLC, <strong>not</strong> the AP (even when connected via console). So we should configure a hostname and IP address for the AP now:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0xAP# capwap ap hostname &lt;hostname&gt;
</span></span><span class="line"><span class="cl">0xAP# capwap ap ip &lt;ip-addr&gt; &lt;netmask&gt; &lt;gateway&gt;
</span></span></code></pre></div><p>Then we can load the new AP image and  WLC image. Depending on which software version you&rsquo;re running today, there is a different command to load the image:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0xAP# ! If you&#39;re running software 8.9 or lower, use the following:
</span></span><span class="line"><span class="cl">0xAP# ap-type mobility-express &lt;TFTP path to AP image&gt; &lt;TFTP path to WLC image&gt;
</span></span><span class="line"><span class="cl">0xAP# ! If you&#39;re running anything above 8.9, use the following:
</span></span><span class="line"><span class="cl">0xAP# ap-type ewc-ap &lt;TFTP path to AP image&gt; &lt;TFTP path to WLC image&gt;
</span></span></code></pre></div><p>Once those commands are submitted, the AP will begin copying the software from the TFTP server. The AP will automatically reboot after the image load is completed.</p>
<p>Quick note: For one of my APs, the WLC software didn&rsquo;t load correctly - and when my AP rebooted I was left with an error that said &ldquo;EWC-AP in Recovery Mode&rdquo;. Re-copying the WLC image fixed it without much trouble. If this happens, the AP will print out the command to re-image the WLC software, but I&rsquo;ll also put it here for reference:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0xAP# ! If you end up in &#34;EWC-AP Recovery Mode&#34;, run the following:
</span></span><span class="line"><span class="cl">0xAP# archive download-sw ewc-ap &lt;TFTP path to WLC image&gt;
</span></span></code></pre></div><p>Okay - Once our AP has come back from loading up all the new software, we can get on with a bit of minimal config required to access the WLC web UI.</p>
<p>First, we&rsquo;ll configure the WLC hostname &amp; our local user account. This can be switched over later to your choice of directory service:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0xC9800eWLC# config t
</span></span><span class="line"><span class="cl">0xC9800eWLC(config)# hostname &lt;hostname&gt;
</span></span><span class="line"><span class="cl">0xC9800eWLC(config)# user-name &lt;admin username&gt;
</span></span><span class="line"><span class="cl">0xC9800eWLC(config-user-name)# password &lt;admin password&gt;
</span></span><span class="line"><span class="cl">0xC9800eWLC(config-user-name)# privilege 15
</span></span></code></pre></div><p>Next we&rsquo;ll also provide the WLC with the administrative credentials used to manage the connected APs:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0xC9800eWLC(config)# ap profile &lt;profile-name&gt;
</span></span><span class="line"><span class="cl">0xC9800eWLC(config-ap-profile)# mgmtuser username &lt;AP admin user&gt; password 0 &lt;AP admin password&gt; secret 0 &lt;AP admin secret&gt;
</span></span></code></pre></div><p>After that, we&rsquo;ll go ahead and configure our basic network settings. This will be the IP address &amp; gateway info that will be used to connect to the WLC web UI and SSH:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0xC9800eWLC(config)# interface gigabit 0 
</span></span><span class="line"><span class="cl">0xC9800eWLC(config-if)# ip address &lt;managemnt IP&gt; &lt;Network mask&gt;
</span></span><span class="line"><span class="cl">0xC9800eWLC(config-if)# no shut
</span></span><span class="line"><span class="cl">0xC9800eWLC(config)# ip default-gateway &lt;Gateway IP address&gt;
</span></span></code></pre></div><p>Lastly - we&rsquo;ll need to actually enable the web server. In my case, I opted to not enable the standard HTTP server. I only enabled the encrypted SSL-enabled web server:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0xC9800eWLC(config)# ! I only enabled the HTTPS server:
</span></span><span class="line"><span class="cl">0xC9800eWLC(config)# ip http secure-server
</span></span><span class="line"><span class="cl">0xC9800eWLC(config)# ! If you wanted to enable the plain-text / unencrypted web server:
</span></span><span class="line"><span class="cl">0xC9800eWLC(config)# ip http server
</span></span><span class="line"><span class="cl">0xC9800eWLC(config)# 
</span></span><span class="line"><span class="cl">0xC9800eWLC(config)# ! Finally - Save the config
</span></span><span class="line"><span class="cl">0xC9800eWLC(config)# exit
</span></span><span class="line"><span class="cl">0xC9800eWLC# wr mem
</span></span></code></pre></div><p>When we save our config for this first time, the AP will verify that the initial configuration pieces have been completed. There is a bit of background cleanup that is performed - which removes any factory config that assisted with provisioning. Once that&rsquo;s all done - we&rsquo;re free to log into the web UI!</p>
<h2 id="embedded-wlc-web-interface">Embedded WLC Web Interface</h2>
<p>I won&rsquo;t spend a lot of time here, as there are far too many things to cover with the new 9800 series controllers. However, I did want to point out a few things that stand out regarding the Embedded WLC.</p>
<p>When we log into our WLC web UI - we&rsquo;ll be able to use the username &amp; password combo that we configured previously. Then we&rsquo;ll be dropped into our WLC dashboard.</p>
<p><img alt="image" loading="lazy" src="/content/images/2020/05/image-2.png#center"></p>
<blockquote>
<p>*Note: Screenshot taken a bit after I did my initial config. This has been running a bit now&hellip;</p></blockquote>
<p>If we click on the number under the <strong>Access Points</strong> heading, we&rsquo;ll be taken to a quick monitoring view of the current connected APs:</p>
<p><img alt="image" loading="lazy" src="/content/images/2020/05/image-3.png#center"></p>
<p>Here we can see the APs we have configured, along with their IP address &amp; status info. We will also see what our <strong>Current Active</strong> WLC controller is, and what our <strong>Current Standby</strong> / <strong>Preferred Active</strong> if we have either configured.</p>
<p>By default, if we boot up a second AP running the embedded WLC software, it will automatically join the WLC cluster as the secondary node. Any WLC config/settings will be copied, then it&rsquo;s ready in case the primary controller fails. In the event of a failure, the secondary WLC will take over - and be accessible using the same IP &amp; login info that we used on the primary.</p>
<p>In my case, I&rsquo;ve also configured one of my APs as a preferred primary controller. For me, this AP is connected to a switch that has a short battery backup - so it&rsquo;s less likely to experience failure. This option can be configured on the individual AP itself. In the left-hand menu, drop into the <strong>Configuration</strong> section, then click on <strong>Access Points</strong> under the <strong>Wireless</strong> header. We&rsquo;ll see a very similar screen to the AP monitoring screen from above. Click on the AP that we want to make our preferred primary, then jump over to the <strong>Advanced</strong> tab. There will be a checkbox for <strong>Preferred Controller</strong>:</p>
<p><img alt="image" loading="lazy" src="/content/images/2020/05/image-4.png#center"></p>
<h2 id="wlc-image-repository">WLC Image Repository</h2>
<p>Last thing - and this one is important. Normally the WLC will have dedicated storage to keep AP software images, which it distributes to new APs when they come online. Unfortunately, while we gain the flexibility and limited footprint of the embedded WLC - we also lose that dedicated storage space. As you might imagine, storage on the AP is limited.</p>
<p>So we&rsquo;ll need to make sure that the WLC has an external image repository that it can use. This configuration can be done via teh web UI and CLI. I&rsquo;ll cover both very quickly here.</p>
<p>From the web UI -  We&rsquo;ll go to <strong>Administration</strong> &gt; <strong>Software Management</strong>:</p>
<p><img alt="image" loading="lazy" src="/content/images/2020/05/image-5.png#center"></p>
<p>Here we&rsquo;ll see our options for where to get images, as well as an inventory of current APs &amp; their images. In the <strong>Mode</strong> drop-down, we&rsquo;ll have the option of tftp, sftp, CCO, and Desktop. TFTP / SFTP are what we&rsquo;re used to. With <strong>Desktop</strong>, we would just upload images directly from our laptop / PC. Interestingly though, <strong>CCO</strong> allows us to provide our Cisco.com credentials - and the controller can pull images directly from Cisco. We&rsquo;ll even have the option to enable automatic software downloads - and specify whether we want to auto-download the latest software release, or only the latest <em>recommended</em> release.</p>
<p>On the CLI side of things - we can accomplish the same using the following commands on our WLC CLI:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0xC9800eWLC(config)# wireless profile image-download &lt;profile name&gt;
</span></span><span class="line"><span class="cl">0xC9800eWLC(config-wireless-image-download-profile)# image-download mode tftp
</span></span><span class="line"><span class="cl">0xC9800eWLC(config-wireless-image-download-profile-tftp)# tftp-image-server &lt;IP address&gt;
</span></span><span class="line"><span class="cl">0xC9800eWLC(config-wireless-image-download-profile-tftp)# tftp-image-path &lt;path to image files&gt;
</span></span></code></pre></div><p>With that, any new APs that join our WLC should be able to auto-load the software necessary.</p>
<hr>
<p>Thanks for reading!</p>
]]></content:encoded>
    </item>
    <item>
      <title>CCIE: Strategy &amp; What&#39;s Next</title>
      <link>https://0x2142.com/ccie-strategy-whats-next/</link>
      <pubDate>Sat, 25 Jan 2020 15:07:32 +0000</pubDate>
      <guid>https://0x2142.com/ccie-strategy-whats-next/</guid>
      <description>The key to the CCIE is having a good strategy. Let&amp;rsquo;s take a look at what helped me</description>
      <content:encoded><![CDATA[<h2 id="things-that-helped">Things That Helped</h2>
<p>One of the big things that helped me was just the experience I had prior to starting on the CCIE. My experience going into the studying likely gave me a huge step up compared to if I tried the exam earlier in my career. If I tried the CCIE eight years ago like I originally wanted to, it would have been a lot more difficult and much more time consuming. I would have had much more to learn from scratch, and much less practical experience to help.</p>
<p>Additionally - the other huge benefit was going into the lab with a solid strategy around time and task management. There were several places through the exam that I felt like I could have easily lost 30-45 minutes on one item. It was very important for me to be able to step back and admit I couldn’t solve something. Instead, it let me focus my time on completing the tasks that I could do - and working on the unknown stuff if I had time later.</p>
<p>On the task management side - I spent time early in the study process on finding a good strategy that worked for me. Once I had this figured out - I used it on <strong>every single</strong> practice lab. I ended up using a combination of a few things other people have written about previously. My base task management was using a great blog post by Chris Miles (<a href="https://thecontrolplane.com/2019/06/21/ccie-strategy-config-section/">Read it here</a>). In Chris’ blog, he suggests breaking up the tasks per location - then completing all the tasks for a location, one location at a time. That part didn’t work for me. Instead, I only used his method of organizing all of the tasks under individual locations - that way I could easily see what tasks were left and where I still needed to work. For example, if I needed to configure EIGRP - I could easily look at the sheet and see every location that needed some form of EIGRP config.</p>
<p>For the actual order in which I implemented tasks, I followed the guidance of a LinkedIn post by Kim Bartlett (<a href="https://www.linkedin.com/pulse/how-i-passed-ccie-routeswitch-lab-first-attempt-kim-bartlett">Link here</a>). In that article, Kim suggests a logical order of operations - like L2, IGP for MPLS, then MPLS, etc. Doing things in this way made sense to me. So I worked out what order worked for me, and decided to follow it. The big difference in my strategy, was that I found it easier to complete all tasks for a certain protocol/technology at once. For example, if I was configuring OSPF - then I would configure it at <strong>every</strong> location at the same time before moving onto the next piece. My overall order of operations was something like this: L2 -&gt; all IGP -&gt; VPN/MPLS -&gt; MP-BGP -&gt; iBGP -&gt; eBGP -&gt; BGP -&gt; IPv6 -&gt; Anything else. I found this to be a good flow for me. It allowed me to configure things like BGP only after I had already configured all of the underlying dependencies - which meant I could test immediately to see if everything was working as intended.</p>
<p>All of the above combined with constant labbing for months prior to the exam was absolutely critical to helping me pass on the first try. I had found a good strategy that worked for me and applied it to every practice lab, which meant that I walked into the actual exam feeling like I had a good way to guide myself through the onslaught of work. Had I walked in with just labbing experience and no good strategy, I don’t think I could have gotten close at all.</p>
<h2 id="okay-now-what">Okay, Now What?</h2>
<p>I’m now getting around to posting this over three months after I passed the CCIE. I’ve spent a lot of time catching up on things around the house, reading books, running through a few video games, and overall just trying to enjoy the free time.</p>
<p>That being said - it wasn’t long for me to start feeling guilty and itching to start working on something else. My first thought was to begin working on the DevNet certifications. I&rsquo;ve been doing a bit of Python &amp; network scripting over the past few years, and I&rsquo;m excited that Cisco is launching a certification program around it. I&rsquo;ve been working on this a bit recently, which has also helped me get back into a few Python projects I hadn&rsquo;t touched in a while. My current plan is to try taking some of these exams shortly after they launch.</p>
<p>I’ve also kept thinking back to one of the other certifications I considered going after: the CCDE. In my current job as a Systems Engineer at Cisco, the content behind this certification applies a lot more to my job than the CCIE. That’s not saying the CCIE doesn’t help me - it absolutely does. However, my job today is more understanding the technologies and how they fit into a customer’s network, rather than performing in-depth configuration work.</p>
<p>I don’t know yet whether I will fully pursue the CCDE and take the exams. But I have started reading a few of the recommended books, and I’m already finding bits of information that are valuable to me. I’m also really enjoying the content and getting much more interested in some of the topics. For now - I am planning on continuing to read through the information just to learn it and see where I can apply it. Once I get a good feel for everything, I’ll decide whether to chase the actual certification or not. For now, I think I&rsquo;ll just enjoy not looking at a PuTTY window for a while 🙂</p>
<p>Thanks for reading - and thanks to all the people who have supported me over the past few years. It’s was a long journey, and not always an easy one - but I think it was well worth it.</p>
<hr>
<p>Started here? Read the rest of my story:</p>
<p><a href="/story-time-how-i-started-working-toward-the-ccie/">Part 1: Getting Started</a></p>
<p><a href="/ccie-written-exam-lab-prep/">Part 2: Written Exam &amp; Lab Prep</a></p>
<p><a href="/ccie-lab-day/">Part 3: Lab Day</a></p>
<p><a href="/ccie-strategy-whats-next/">Part 4: Lab Strategy &amp; What&rsquo;s Next</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>CCIE: Lab Day</title>
      <link>https://0x2142.com/ccie-lab-day/</link>
      <pubDate>Sat, 25 Jan 2020 15:07:07 +0000</pubDate>
      <guid>https://0x2142.com/ccie-lab-day/</guid>
      <description>Let&amp;rsquo;s talk about my experiences with taking the CCIE Routing &amp;amp; Switching lab!</description>
      <content:encoded><![CDATA[<p>In the weeks leading up to the lab exam - I felt very unsure of where I was at. On one side, I felt like I was doing pretty well at most of the practice labs I was working on. But on the other side, I felt like I didn’t have any true idea of what challenges the real exam would hold - so I could be missing something big and have no idea yet. I know some people will throw the exam blueprint into excel and give themselves ratings on how well they know a particular blueprint item - but I never got into using this after trying it a few times. Realistically, I should have forced myself to do this anyways. Then I would have had a more deterministic way to judge how prepared I was. Instead - I had just reached a point where I knew I just needed to take the actual exam and figure out what I didn’t know yet.</p>
<p>Lab day finally came - and I arrived at Cisco building 5 in Richardson, TX around 7:45am. There were already a handful of other CCIE candidates waiting outside for the building to open. Once it hit 8am, we all went in to get signed in and fill out our lunch order forms. Then it was time to wait.</p>
<p>The exam proctor showed up around 8:17 and guided us to the exam room. I figured there would be more time allotted to the proctor talking through rules, guidelines, etc… but instead he just said a few quick things and we were told to begin.</p>
<h2 id="troubleshooting">Troubleshooting</h2>
<p>The troubleshooting section had me a bit concerned. It’s always difficult to jump into a completely unknown network and try to fix a problem - and this was no different. My first question immediately made me start panicking a little. I read the ticket, looked at the expected output - and began wondering where to start while being very aware of my short time limit. Every question felt like “I’m never going to figure this out in time” - yet after a few minutes of troubleshooting I was able to find the answers to the first few questions.</p>
<p>Halfway through the section I received a few tickets that required a lot more work. Some of these I didn’t make much progress on, and some I was able to get half-way resolved. For each of these I tried very hard to keep to a reasonable time limit per question, then mark it down as something to come back to later if I had time.</p>
<p>A lot of people talk about counting your points during the exam to know where you stand. I had originally assumed that this would just be a waste of time. Yet when I finished going through the remaining tickets, I knew I had to make sure I had enough points. Turned out I was barely on the edge of a passing score - assuming I had resolved all of the tickets correctly. My first two hours ran out, and I got the 30 minute warning. I was hoping to avoid using the extra 30 minutes, but I knew I needed to go back to the 3-4 questions I hadn’t completed.</p>
<p>About 15 minutes later - I had managed to figure out one or two more of the tickets and decided to give up on the remaining items. Based on my estimated point count - I should have been in a good spot on the troubleshooting section….. But I still wasn’t confident in all of my answers. I knew I had a ticket or two that might not be resolved in the correct way. I decided to save the remaining 15 minutes and just move onto the next part of the exam.</p>
<h2 id="diagnostics">Diagnostics</h2>
<p>Next was the diagnostics section. My biggest complaint here (and it&rsquo;s somewhat minor) is that the on-screen timer is located in a completely different place than troubleshooting &amp; config. At first (probably because I was in a rush), I couldn’t find the timer - and I also had not kept track of when I began the section. That was a big mistake on my part. So I forced myself to rush through the section, knowing it could end unexpectedly at any second.</p>
<p>Once I wrapped up my diag questions - I finally found the timer… and to my surprise had just under five minutes left. Not a ton of time, but enough for me to go back and double check a few answers that I had rushed myself through. I also used the last minute or two to run for a restroom break before starting the config section.</p>
<p>I honestly had no idea how well I was doing on this section. One of the questions seemed straightforward, but the answer I picked felt too simple. But maybe I was just overthinking it? The other questions made me waffle back and forth between a few answers. In the end, I just went with what my instincts told me was the most likely answer and just stuck with that.</p>
<h2 id="config">Config</h2>
<p>The config section is extremely overwhelming at first. Well, I suppose it doesn’t get any less overwhelming during the exam - but you quickly get busy enough to stop caring about that 🙂</p>
<p>I had about 30-45 minutes in the config section before we took lunch. That was enough time for me to get through all of the Layer 2 tasks quickly and then build out my task list on the scratch paper. During this time, I thought I was doing okay until I got to the end of one of my first tasks. I had just completed all of the items within that task when I read the last item - which made me realize I had done the entire task incorrectly. That was not a pleasant feeling. Luckily, I caught my mistake before moving on - but the time had already been wasted and now I had to go back and re-configure that entire section.</p>
<p>Lunch was quick. We went out, ate our food, then got back to the exam in less than 15-20 minutes. There was a bit of minor discussion - but not a whole lot.</p>
<p>The remainder of the day went by very quickly. As I had practiced during the prior weeks of practice labs, I placed my trust in strategy &amp; order of operations - then just went heads down and got to work. I tried not to look at the clock and instead just focused on getting the tasks done as quickly and efficiently as possible. I’ll share a little more on my strategy in the next post.</p>
<p>I ran into a few problems here and there throughout the exam, but nothing too crazy. The strategy I used allows for quick connectivity/functionality testing after completing a task, which allowed me to find and fix my errors quickly. Similar to the troubleshooting section, I hit a few tasks that I could only figure out parts of - so I marked them down to follow up later and just moved on. Since you don’t get partial credit for tasks, I knew I would need to circle back to these if I wanted a shot at passing - but there is no sense in wasting too much time on one task if I couldn&rsquo;t figure it out quickly.</p>
<p>By the time I had finished every task, I finally let myself check the clock. I was shocked to see I still had almost a full hour remaining. I quickly took advantage of the time to go back to the several sections I needed more work on. A few of these I stumbled through until I was able to find my problems - and some of it I had to crack open the documentation site to figure out what I needed to do.</p>
<p>Running through a lot of the verification steps - there was still a few things not working as they should. I spent time troubleshooting, changing configs, and finally figuring out a few things. I made quite a few configuration changes here to force a few things to work, but I wasn’t sure if they were valid solutions - or if I would end up losing points for doing things I shouldn’t have.</p>
<p>In the last 10 or so minutes, I tried to very quickly add up my points while performing a quick skim through the tasks again. Being that close to the end of the exam - it made me feel a bit sick to start finding additional items I had missed. I rushed to throw in a few last-minute changes, then retest to make sure nothing broke in the process. I didn’t make it through re-reading all of the tasks, so I was left wondering what else I might have missed.</p>
<p>Assuming I had not missed anything else - my count of points placed me in a fairly decent spot on config. However, since there is an overall cut score for the entire exam - I had no idea if I would have enough total points between all three sections to pass. I was already like I might have just barely scraped enough points together for troubleshooting, and diag felt like a complete wildcard.</p>
<p>When I left the exam center, I found myself feeling much better than when I had entered. If I passed, then that would be awesome. And if I had failed, then at least I was confident in what I needed to go back and study. Rather than having to keep worrying about what tricks the exam might hold, I now had the experience of knowing what to expect. I was happy to have attempted the exam once - and knew I would be far better prepared the next time.</p>
<p>That evening I went to dinner with a few CCIE candidates who would be attempting the exam the following day. Just tried to have a good time, and not check my email too much :). When I got back to the hotel that night, I still had no results yet - so I just went to bed and tried to get some sleep.</p>
<h2 id="the-next-day">The Next Day</h2>
<p>I woke up probably a dozen or more times throughout the night. Every time my first instinct was to grab my phone and see if I had gotten my results yet. Every time I forced myself to <strong>not</strong> check, and just go back to sleep. Around 5am, I finally let myself check once - but still had nothing.</p>
<p>I finally got up around 6:30 - and the CCIE exam site was down. I had a bunch of text messages from people back home asking if I had anything to report - but now I couldn’t even check the site. Later I would find out that the site was broken due to an internal issue at Cisco, but for the time I couldn’t do anything. I tried a few more times throughout the morning, but mostly just gave up and decided to wait it out.</p>
<p>My flight left around 10:30 am. While waiting in the airport, I still kept checking every so often but could not get to the site.</p>
<p>Once I got onto the plane, the site finally loaded! But my results were the same: No score yet. A this point I figured I would just give up, enjoy the flight - and check when I got back home.</p>
<p>Boarding took a little longer than usual for the remaining passengers. Right as it was announced that they were shutting the doors and we would be taking off shortly, I decided to try checking one more time.</p>
<p>As the site loaded - this time I was greeted with a new status: <strong>Pass</strong>.</p>
<p>My initial reaction was just absolute relief to finally be done - knowing that I didn’t have to keep worrying about trying to pass before the upcoming certification changes. I sat back for a minute before refreshing the site again to make sure the result didn’t change. Nope - the result still said pass.</p>
<p>With that - on October 9th, 2019 - I was done. I had my number. CCIE #63461.</p>
<hr>
<p>Keep going for the rest of my story:</p>
<p><a href="/story-time-how-i-started-working-toward-the-ccie/">Part 1: Getting Started</a></p>
<p><a href="/ccie-written-exam-lab-prep/">Part 2: Written Exam &amp; Lab Prep</a></p>
<p><a href="/ccie-lab-day/">Part 3: Lab Day</a></p>
<p><a href="/ccie-strategy-whats-next/">Part 4: Lab Strategy &amp; What&rsquo;s Next</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>CCIE: Written Exam &amp; Lab Prep</title>
      <link>https://0x2142.com/ccie-written-exam-lab-prep/</link>
      <pubDate>Sat, 25 Jan 2020 15:06:47 +0000</pubDate>
      <guid>https://0x2142.com/ccie-written-exam-lab-prep/</guid>
      <description>A short look at my experiences studying &amp;amp; taking the Cisco CCIE written exam</description>
      <content:encoded><![CDATA[<h2 id="written-exam">Written Exam</h2>
<p>Finally in early 2019 I gave up on trying to gauge where I was at - and figured it was time to just give the exam a shot. I had already been studying for almost a year and a half, and I was craving some definitive way of figuring out where I was at. I went ahead and scheduled an exam for Tuesday, March 12th.</p>
<p>When I walked into the written exam, my first question immediately made me feel unprepared. It was something specific to provider WAN switching - not a topic I had spent enough time on yet. I did my best to take an educated guess, but that first question gave me a lot of doubt about how well prepared I was.</p>
<p>The written exam overall felt very&hellip; all over the place. It didn’t feel like a single cohesive exam - instead it felt like 20 different banks of questions shuffled into one. Some people call the exam just random networking trivia - and in some ways that might be accurate. For example, I might have a question on very basic L2, followed immediately by a very in-depth question on MPLS. Then probably over to something completely different. I didn’t want to admit it at the time, but I probably felt far less confident in answering many of the questions I got - and gave my best effort on guessing at quite a few.</p>
<p>Already not feeling great about how well I was doing, the test finally made its way into the evolving technologies section. This section did nothing to ease my nerves :). I completely understand why this section exists, but it felt like there was almost no effort put into some of the questions. Many of the questions I got made no sense, had grammatical errors, or gave a set of possible answers that didn’t line up with what the question was asking. Even for technologies that I did have a lot of experience with, it felt like the question was just written by someone who had no understanding of it.</p>
<p>As I finished my last question, there was no doubt in my mind that I had failed. To me, it was just a matter of how badly did I miss and how can I better prepare for next time. I was already making several mental notes on what topics I desperately needed to go back and review for the next attempt.</p>
<p>However - when I clicked through the remaining screens on the exam, I was extremely surprised to see that I had passed. It was only by a few points - but a pass is a pass!</p>
<p>Walking out of the exam, I sent a message to a few people at work to let them know I had passed. Even with the score sheet in my hand, I didn’t feel comfortable saying that I had passed. At no point during the exam did I feel like I was doing well. Maybe that’s just part of the difficulty? I don’t know&hellip; I&rsquo;m honestly glad to see the written exam requirement is being dropped from the new exam blueprints.</p>
<h2 id="studying-for-the-lab-exam">Studying for the Lab Exam</h2>
<p>Once I had gotten past the written exam, my full attention went into working toward the lab. I spent too much time initially trying to get my lab environment all sorted out. Went back and forth trying to choose between EVE-NG and GNS3, before finally settling on GNS3. Then I wasted a bunch of time trying to find the right images to use and testing them to make sure everything worked.</p>
<p>Finally - I picked up a copy of “CCIE Routing and Switching v5.1 Foundations: Bridging the Gap Between CCNP and CCIE” and got started. Going through this first book was far less enjoyable than I had hoped. Each lab was a completely different topology with a lot of pre-work to get going - and in many cases completing the actual practice lab would take a fraction of the time it took to get set up. I got frustrated with this a lot - but tried to keep pushing through to at least finish the book as a starting point. This ultimately amounted to a rocky start to labbing for me. Not working on it as much as I should, and not necessarily looking forward to it.</p>
<p>My next set of materials would be the INE workbooks - which honestly are structured far better. These labs were all on a shared topology that I could easily clone in GNS3 every time I started a new section. All of the pre-config is done for you - so that you can just focus on the pieces relevant to the topic. For example, if you’re working on a BGP lab - you don’t have to start from scratch with IP addressing or L2 configs. This made the content much easier to consume, and did a lot to help me spend more time working on practice labs. I got through these labs pretty quickly and repeated quite a few for additional practice.</p>
<p>At Cisco Live US 2019 - there was a huge announcement regarding certification changes. The CCIE exam &amp; content was changing (along with pretty much everything else). I wasn’t entirely surprised to hear the announcement since the existing track was several years old, and I had come across a few rumors on the internet of possible changes. Even still, I was finding myself now up against a very finite amount of time to pass the lab exam. The old test would be phased out in just eight months (in February 2020).</p>
<p>After the announcement, I talked to my manager about what to do. We decided it would probably be in my best interests to schedule a lab date, and do whatever I can to try and pass ahead of the exam changes. So - only a few days after the new content was announced, I had scheduled a lab date for October 9th, 2019. This was less than four months away, and I still had a ton of content / practice labs to get through.</p>
<p>Having the looming deadline did great things for my motivation :). On the good side of things - It helped me to spend more and more time studying for the lab exam. I was able to focus more than before, and I was finding it much easier to push myself to practice even when I wasn&rsquo;t necessarily excited to. Over the summer I nearly doubled the amount of time I had spent labbing compared to before the announcement. On the not-so-good side - I had also put together a week-by-week plan of what I still needed to accomplish between now and October. It was a tighter timeline than I was originally looking at, and now it felt like I didn’t have enough time to accomplish everything. I pushed through it anyways, knowing that October was just my first attempt. If I couldn’t finish everything in time, then I would still have time before the second try.</p>
<p>Remember back when I mentioned that six year gap between getting the CCNP and starting on the CCIE? This is the big part where that helped me a ton. Going through a lot of the workbooks - I didn’t necessarily feel like anything was too crazy. Over the past 10+ years I’ve worked at a number of different companies and had the opportunity to play with a lot of networking gear. I had a great base of experience with most L2/L3 technologies, including quite a bit of practice with all the fun that BGP has to offer.</p>
<p>One of the other big things that I think helped was that not all of my prior experience was on Cisco equipment. Having to learn how to configure BGP, VRFs, or switching on multiple vendors forces you to think beyond the syntax. Every vendor implements things in their own unique way - and this helps you to get beyond just memorizing what commands to enter. Instead, you begin having to learn much more about the underlying technologies and how they operate - and understanding what you’re actually trying to accomplish. Then it’s just a matter of researching whatever syntax that specific vendor uses to implement that function.</p>
<p>Having that good base of knowledge and experience helped me burn through the practice labs fairly quickly. A lot of content felt very familiar, with maybe a few new variations of commands - or maybe a new option that I hadn’t previously used. Even some of the pieces that I hadn’t used much of before, like DMVPN or multicast, still seemed easy enough to grasp how it worked and learn the necessary syntax.</p>
<p>That being said - In a lot of ways it also gave me a false sense of security. Feeling like maybe I knew more than I realized and therefore maybe I was better prepared. Yet at the same time, knowing how difficult the lab is supposed to be - and constantly wondering what I could be missing.</p>
<hr>
<p>Keep going for the rest of my story:</p>
<p><a href="/story-time-how-i-started-working-toward-the-ccie/">Part 1: Getting Started</a></p>
<p><a href="/ccie-written-exam-lab-prep/">Part 2: Written Exam &amp; Lab Prep</a></p>
<p><a href="/ccie-lab-day/">Part 3: Lab Day</a></p>
<p><a href="/ccie-strategy-whats-next/">Part 4: Lab Strategy &amp; What&rsquo;s Next</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>Story Time! How I Started Working Toward the CCIE</title>
      <link>https://0x2142.com/story-time-how-i-started-working-toward-the-ccie/</link>
      <pubDate>Sat, 25 Jan 2020 15:06:07 +0000</pubDate>
      <guid>https://0x2142.com/story-time-how-i-started-working-toward-the-ccie/</guid>
      <description>Why &amp;amp; how I started studying for the CCIE a few years ago</description>
      <content:encoded><![CDATA[<p>Now that we&rsquo;re firmly into 2020 - I finally decided it was about time to get this posted. I actually wrote most of this shortly after passing the exam, but it just sat unedited and collecting dust since them.</p>
<p>In about a month, most of the exams will be changing over to the new blueprints so I&rsquo;m not sure how relevant any of this will be - but it&rsquo;s still worth throwing out there, right?</p>
<h2 id="why-ccie-why-now">Why CCIE? Why now?</h2>
<p>The two years I spent working on the CCIE dragged on for what seems like forever. Back in late 2017, I had hit a point where I felt like I wasn’t being challenged enough technically - and I missed the old days of excitement when I was studying/labbing for certifications exams. I had always wanted to go after the CCIE for a number of reasons, but it never made sense before. I had decided that maybe it was finally time to give it a shot.</p>
<p>To step back for just a moment - I originally began my career in networking by taking advantage of the Cisco Networking Academy program, which had been offered at my high school. It’s hard to believe I started that over 14 years ago - but it was likely the single most influential thing in getting me where I’m at in my career today. After two years of classes, I walked out in late 2007 with my CCNA and eager to begin working in networking.</p>
<p>Over the next few years - I worked on a number of additional certifications. I always had fun going after certifications because they gave me a path to follow and a goal to achieve. They helped to make the process of learning a bit more fun. On the Cisco side of things, I worked on the CCDA, CCNA Voice (now retired), and my CCNA Security. Finally in 2011 I finished up my CCNP and had to figure out what was next. I was super interested in the CCIE - but there was no way my company would pay for it. For the time I shelved the idea - but I didn’t give up on it as a goal. Instead, I just continued to maintain &amp; recertify my existing certs, and picked up the CCDP along the way.</p>
<p>Fast forward to late 2017. I had officially passed my 10 year anniversary on my CCNA. I was also feeling like I was hitting a wall in my technical abilities. I wanted to do something different and fun - and my first thought went back to pursuing a new certification because of how much I used to enjoy the process. I debated between a handful of certs, including CISSP, CCNP Security, CCDE, and CCIE R&amp;S. After giving it some thought and talking to a few people, I decided it was finally time to tackle the CCIE and work toward one of my long-standing goals. That six year gap between CCNP and starting on the CCIE would come back to cause me a lot of problems, but also help me in a few ways I hadn’t expected - both of which I’ll talk about later.</p>
<h2 id="time-to-study">Time to Study</h2>
<p>On October 4th, 2017 - I ordered by first set of books and began studying for the CCIE Routing &amp; Switching written exam.</p>
<p>To be absolutely honest, I had no plan going into this. Historically when I took certification exams my process was usually watching a set of training videos (usually CBT Nuggets), reading through the official cert guides a few times, picking up maybe another book or two, taking a bunch of notes, then a lot of labbing. It was never enough for me to just watch/read about the stuff - I needed to get hands on and break it to really learn. Usually by the time I had finished all of that, I would be feeling confident enough to go give the test a shot. I went into the CCIE written assuming this strategy would still probably work - and I was absolutely wrong.</p>
<p>When I began working through the books and videos I had - I found that I wasn’t getting as excited about it as I had hoped. In fact, it just felt like so much of the content was just review of things I had learned years ago during CCNP studies. That long gap since my CCNP also left me reluctant to want to memorize all of the little details again. How many things had I studied for the CCNP that I never used in my actual job? I certainly didn’t want to waste the time trying to re-learn/re-memorize those things now&hellip; But I knew I would need to if I wanted to pass the exam. This kinda killed my motivation in some ways - because I would end up having to force myself to try and retain information that I didn’t want to.</p>
<p>Studying for the written was hard for me - and probably more than it should have been. Between the mixed motivation, I was also working through a lot of stress and nonsense in both my personal and work life. I would eventually work through these issues - but sometimes it would mean having to take a few weeks off from studying.Every time I took a break, I knew I needed to - yet it was still very demoralizing.</p>
<p>I got some help toward my goal in June 2018: I had the opportunity to take a job working at Cisco as a Systems Engineer. In terms of working toward the CCIE, this was an absolute key step in getting there. I was finally working for a company that was willing to encourage and help me toward my goal. I was also surrounded by a ton of engineers and enthusiastic networking professionals who were there to support me. I got to spend time with other people who were working on certifications, and even network engineers at my customers who always wanted to ask how my studies were going. This helped a lot to get me back into being excited about the content - and brought a bit of motivation back.</p>
<p>Even though I was spending a lot of time studying for the written exam - I never really felt like I was making true progress. I believe this was likely caused by the fact that the exam blueprint is so large and diverse. I never settled on a good method to reliably track how far I had progressed on all of the content. While I felt like I had learned a lot, I also perpetually felt like I was nowhere close to where I needed to be. I also have an old habit of waiting to schedule the exam until after I already already feel confident I have a good shot at passing. With the CCIE written, I felt like that level of confidence was never going to happen.</p>
<hr>
<p>Keep going for the rest of my story:</p>
<p><a href="/story-time-how-i-started-working-toward-the-ccie/">Part 1: Getting Started</a></p>
<p><a href="/ccie-written-exam-lab-prep/">Part 2: Written Exam &amp; Lab Prep</a></p>
<p><a href="/ccie-lab-day/">Part 3: Lab Day</a></p>
<p><a href="/ccie-strategy-whats-next/">Part 4: Lab Strategy &amp; What&rsquo;s Next</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>How to: Catalyst 9800 MAC Filtering</title>
      <link>https://0x2142.com/how-to-catalyst-9800-mac-filtering/</link>
      <pubDate>Tue, 12 Nov 2019 15:35:07 +0000</pubDate>
      <guid>https://0x2142.com/how-to-catalyst-9800-mac-filtering/</guid>
      <description>A tutorial on configuring MAC address filtering on a Cisco 9800 WLC</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/rWupjgsF0HM?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><strong>Update 2020 / 05 / 19</strong> - I&rsquo;ve added a video above that walks through the steps detailed in this blog post.</p>
<blockquote>
<p>If you&rsquo;re using the &lsquo;Basic&rsquo; Wireless setup, you may see an error when trying to apply the policy: &ldquo;switch 1 dbm wireless Use of default ACL preauth v4 is not permitted&rdquo;</p>
<p>If you come across this error, it&rsquo;s a known bug (<a href="https://quickview.cloudapps.cisco.com/quickview/bug/CSCvt18875">CSCvt18875</a>) specific to only the &lsquo;Basic&rsquo; setup wizard (which is what I used in this post below). If so, check out the video above which walks through the &lsquo;Advanced&rsquo; setup and bypasses this error.</p></blockquote>
<hr>
<p>I&rsquo;ve been spending a bit of time over the past few weeks building up a wireless lab. Trying to get a good understanding of how the new Catalyst 9800 wireless controller works, and how it differs from some of the previous iterations.</p>
<p>In order to play around with the new controller, I decided to try to build a new configuration that mimics my current home wireless. Today I am using Ubiquiti APs, which come with their own free controller software. Most of my current config is fairly straightforward - a few SSIDs, two APs, and a guest network with captive portal. One of my SSIDs is dedicated to any IoT devices and is more restrictive than the other networks. This network uses both a pre-shared key for authentication as well as MAC-based filtering.</p>
<p>In this post - we&rsquo;ll walk through how to set up a new SSID with client MAC filtering.</p>
<p><em>Note: This was written using Catalyst 9800-CL version 16.12.1s. APs are configured in flexconnect with local authentication (no AAA, ISE, etc)</em></p>
<hr>
<p>While this post is not focused on in-depth WLAN config, we will start by quickly setting up a new network.</p>
<p>Once you get logged into the controller - we&rsquo;ll click on the <strong>Wireless Setup</strong> icon in the upper right-hand side. Then drop down to the <strong>Basic</strong> option:</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/New-wireless-net-01.jpg#center"></p>
<p>This takes us through a pretty quick and easy wizard to set up our new location &amp; wireless networks. At first, we will have no locations configured - so we will click <strong>Add:</strong></p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/New-wireless-net-02-.jpg#center"></p>
<p>We&rsquo;ll start off building our location by giving it a name and description. These are used for some naming of policy objects within the WLC, so make sure to use a name that makes sense.</p>
<p>In my case, I&rsquo;m also going with a flexconnect deployment - so we&rsquo;ll select that option and also provide the AP native VLAN.</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/New-wireless-net-03.png#center"></p>
<p>Next, we&rsquo;ll click over to the <strong>Wireless Networks</strong> tab. This is where we will create our WLAN and apply the initial configuration. We don&rsquo;t have any networks yet, so click <strong>Add</strong></p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/New-wireless-net-04.jpg#center"></p>
<p>The two primary things we need to address are highlighted in red below. We need to create a WLAN and assign it to a VLAN. Let&rsquo;s start with the WLAN by clicking <strong>Define new.</strong></p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/New-wireless-net-05.png#center"></p>
<p>On the <strong>General</strong> tab, we&rsquo;ll give this WLAN a profile name and a SSID.</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/New-wireless-net-06.jpg#center"></p>
<p>Next, we&rsquo;ll hop on over to the <strong>Security</strong> tab, and focus on the <strong>Layer 2</strong> sub-tab. The first thing I want to point out - is that at this point, we will not be enabling the <strong>MAC Filtering</strong> checkbox. We&rsquo;ll need some additional config first, then come back to this later.</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/New-wireless-net-07.jpg#center"></p>
<p>Scroll down to the bottom of the window and there will be some settings for your authentication. By default, <strong>802.1x</strong> will be enabled. This post won&rsquo;t cover how to setup/configure that. Instead, we&rsquo;ll be deselecting <strong>802.1x</strong> and checking the box for <strong>PSK.</strong> Then a text field will appear for us to enter the <strong>Pre-Shared Key</strong>.</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/New-wireless-net-08.jpg#center"></p>
<p>Once we click <strong>Apply to Device</strong>, we&rsquo;ll finish up by assigning our <strong>VLAN or VLAN Group</strong>. In this case, I have already created a VLAN named <strong>IoT</strong>. If you haven&rsquo;t created a VLAN yet, you can do so by going to <strong>Configuration</strong> &gt; <strong>Layer 2</strong> &gt; <strong>VLAN.</strong> Then add a new VLAN under the <strong>VLAN</strong>tab.</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/New-wireless-net-09.png#center"></p>
<p>Click <strong>Add</strong>, then we&rsquo;ll be back to the wizard and see the new WLAN we just created.</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/New-wireless-net-10.jpg#center"></p>
<p>In order to finish up with the wizard, we just need to assign our Access Points. Click on the <strong>AP Provisioning</strong> tab. If you have already configured APs to join to this controller, you will see them on the left side under <strong>Available APs</strong>. Check which ones to apply this WLAN to, then click the arrow to move them to <strong>APs on this location.</strong></p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/New-wireless-net-11.jpg#center"></p>
<p>Click <strong>Apply</strong>, and that will finalize all of the configuration we just did - then drop us back to the <strong>Wireless Setup</strong> page.</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/New-wireless-net-12.jpg#center"></p>
<p>Okay - Now that we have that completed, we can move onto creating our MAC filtering policies.</p>
<p>Back in the menu - Let&rsquo;s go to <strong>Configuration</strong> &gt; <strong>Security</strong> &gt; <strong>AAA</strong></p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/mac-auth-01.jpg#center"></p>
<p>In this section - we first need to create an Authorization policy. Select the <strong>AAA Method List</strong> tab, then <strong>Authorization</strong>, then <strong>Add</strong> to create the new policy.</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/mac-auth-02.jpg#center"></p>
<p>In here we&rsquo;ll specify a name, then select <strong>Type: network</strong>, and <strong>Group Type: local</strong>. Then go ahead and <strong>Apply to Device</strong></p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/mac-auth-03.jpg#center"></p>
<p>Once we have that, let&rsquo;s go over to the <strong>AAA Advanced</strong> tab, and click <strong>Add</strong> in the <strong>Attribute List Name</strong> section.</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/mac-auth-04.jpg#center"></p>
<p>Here we need to provide the SSID we want our MAC policy to apply to. Under <strong>Attribute Type</strong>, select <strong>SSID.</strong> Then under <strong>Attribute Value</strong>, select the target SSID that our policy will be tied to.</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/mac-auth-05.jpg#center"></p>
<p>Don&rsquo;t forget to click <strong>Save</strong> on the attribute before clicking <strong>Apply to Device</strong>!</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/mac-auth-06.jpg#center"></p>
<p>Time to input our list of device MAC addresses! Drop into the <strong>Device Authentication</strong> section, and click <strong>Add</strong> - or upload a CSV file if you have one.</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/mac-auth-07.jpg#center"></p>
<p>Input the device <strong>MAC Addrees</strong> and select the <strong>Attribute List Name</strong> that we configured just a minute ago. Then <strong>Apply to Device</strong></p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/mac-auth-08-1.jpg#center"></p>
<p>After that - we should have our completed list of MAC addresses which will be permitted to join our wireless network. All we need to do is go back to our WLAN and enable MAC filtering.</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/mac-auth-09.jpg#center"></p>
<p>Let&rsquo;s go to <strong>Configuration</strong> &gt; <strong>Tags &amp; Profiles</strong> &gt; <strong>WLANs</strong></p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/config-wlans-01.jpg#center"></p>
<p>This should give us the list of any configured WLANS - including the one we created earlier. Go ahead and click on it to edit.</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/config-wlans-02.jpg#center"></p>
<p>Next, we&rsquo;ll jump straight to the <strong>Layer 2</strong> section under the <strong>Security</strong> tab. Check the box for <strong>MAC Filtering</strong> and select the <strong>Authorization List</strong> we created from the drop down.</p>
<p><img alt="image" loading="lazy" src="/content/images/2019/11/config-wlans-03.jpg#center"></p>
<p>And we&rsquo;re done! Clients that want to join our newly created SSID will need the pre-shared key we configured, but they will also need to be manually added to our MAC address filter as well.  While this isn&rsquo;t a perfect security measure since MAC addresses can be easily spoofed - it does add an extra layer of protection to keep unauthorized devices from inadvertently being able to join this specific WLAN.</p>
<hr>
<p>In the event that a client is NOT on the authorized list, you may see the following logs in a client debug:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">2019/10/25 22:26:12.327 {wncd_x_R0-0}{1}: &amp;#91;client-orch-sm] &amp;#91;22573]: (note): MAC: aaaa.aaaa.aaaa  Association received. BSSID aaaa.aaaa.b34d, old BSSID 0000.0000.0000, WLAN SuperSecret, Slot 1 AP aaaa.aaaa.b340, AP_3802-1F01
</span></span><span class="line"><span class="cl">2019/10/25 22:26:12.327 {wncd_x_R0-0}{1}: &amp;#91;client-orch-state] &amp;#91;22573]: (note): MAC: aaaa.aaaa.aaaa  Client state transition: S_CO_INIT -&gt; S_CO_ASSOCIATING
</span></span><span class="line"><span class="cl">2019/10/25 22:26:12.328 {wncd_x_R0-0}{1}: &amp;#91;client-orch-state] &amp;#91;22573]: (note): MAC: aaaa.aaaa.aaaa  Client state transition: S_CO_ASSOCIATING -&gt; S_CO_MACAUTH_IN_PROGRESS
</span></span><span class="line"><span class="cl">2019/10/25 22:26:12.328 {wncd_x_R0-0}{1}: &amp;#91;client-auth] &amp;#91;22573]: (note): MAC: aaaa.aaaa.aaaa  MAB Authentication initiated. Policy VLAN 0, AAA override = 0, NAC = 0
</span></span><span class="line"><span class="cl">2019/10/25 22:26:12.329 {wncd_x_R0-0}{1}: &amp;#91;ewlc-infra-evq] &amp;#91;22573]: (note): Authentication Success. Resolved Policy bitmap:11 for client aaaa.aaaa.aaaa 
</span></span><span class="line"><span class="cl">2019/10/25 22:26:12.329 {wncd_x_R0-0}{1}: &amp;#91;ewlc-infra-evq] &amp;#91;22573]: (ERR): SANET_AUTHC_FAILURE - AAA Server Down username ac37434a673a, audit session id 000000000000006B3E9D2A55, 
</span></span><span class="line"><span class="cl">2019/10/25 22:26:12.331 {wncd_x_R0-0}{1}: &amp;#91;client-orch-state] &amp;#91;22573]: (note): MAC: aaaa.aaaa.aaaa  Client state transition: S_CO_MACAUTH_IN_PROGRESS -&gt; S_CO_ASSOCIATING
</span></span><span class="line"><span class="cl">2019/10/25 22:26:12.331 {wncd_x_R0-0}{1}: &amp;#91;dot11] &amp;#91;22573]: (ERR): MAC: aaaa.aaaa.aaaa  Failed to assoc failure tr state entry. Incorrect validation status value :1
</span></span><span class="line"><span class="cl">2019/10/25 22:26:12.331 {wncd_x_R0-0}{1}: &amp;#91;dot11] &amp;#91;22573]: (ERR): MAC: aaaa.aaaa.aaaa  Dot11 update co assoc fail. Sent assoc failure to CO. delete reason: 9, CO_CLIENT_DELETE_REASON_MAB_FAILED
</span></span><span class="line"><span class="cl">2019/10/25 22:26:12.331 {wncd_x_R0-0}{1}: &amp;#91;client-orch-sm] &amp;#91;22573]: (note): MAC: aaaa.aaaa.aaaa  Client delete initiated. Reason: CO_CLIENT_DELETE_REASON_MAB_FAILED, fsm-state transition
</span></span></code></pre></div><p>On the opposite side, when a client is successfully able to pass MAC authentication - the logs will show the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">2019/10/25 21:25:53.148 {wncd_x_R0-0}{1}: &amp;#91;client-auth] &amp;#91;22573]: (note): MAC: aaaa.aaaa.aaaa  MAB Authentication initiated. Policy VLAN 0, AAA override = 0, NAC = 0
</span></span><span class="line"><span class="cl">2019/10/25 21:25:53.150 {wncd_x_R0-0}{1}: &amp;#91;ewlc-infra-evq] &amp;#91;22573]: (note): Authentication Success. Resolved Policy bitmap:11 for client aaaa.aaaa.aaaa 
</span></span><span class="line"><span class="cl">2019/10/25 21:25:53.151 {wncd_x_R0-0}{1}: &amp;#91;client-auth] &amp;#91;22573]: (note): MAC: aaaa.aaaa.aaaa  MAB Authentication success.
</span></span><span class="line"><span class="cl">2019/10/25 21:25:53.151 {wncd_x_R0-0}{1}: &amp;#91;client-orch-state] &amp;#91;22573]: (note): MAC: aaaa.aaaa.aaaa  Client state transition: S_CO_MACAUTH_IN_PROGRESS -&gt; S_CO_ASSOCIATING
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">2019/10/25 21:30:08.626 {wncd_x_R0-0}{1}: &amp;#91;client-orch-sm] &amp;#91;22573]: (debug): MAC: aaaa.aaaa.aaaa  Received Dot11 association request. Processing started,SSID: SuperSecret2, Policy profile: Home_WLANID_3, AP Name: AP_3802-3F01, Ap Mac Address: aaaa.aaaa.c9a0 BSSID MAC aaaa.aaaa.b34d wlan ID: 3RSSI: 0, SNR: 32
</span></span><span class="line"><span class="cl">2019/10/25 21:30:08.626 {wncd_x_R0-0}{1}: &amp;#91;client-orch-state] &amp;#91;22573]: (note): MAC: aaaa.aaaa.aaaa  Client state transition: S_CO_L2_AUTH_IN_PROGRESS -&gt; S_CO_L2_AUTH_IN_PROGRESS
</span></span><span class="line"><span class="cl">2019/10/25 21:30:08.626 {wncd_x_R0-0}{1}: &amp;#91;dot11] &amp;#91;22573]: (info): MAC: aaaa.aaaa.aaaa  DOT11 state transition: S_DOT11_ASSOCIATED -&gt; S_DOT11_MAB_PENDING
</span></span><span class="line"><span class="cl">2019/10/25 21:30:08.626 {wncd_x_R0-0}{1}: &amp;#91;client-orch-state] &amp;#91;22573]: (note): MAC: aaaa.aaaa.aaaa  Client state transition: S_CO_L2_AUTH_IN_PROGRESS -&gt; S_CO_MACAUTH_IN_PROGRESS
</span></span><span class="line"><span class="cl">2019/10/25 21:30:08.626 {wncd_x_R0-0}{1}: &amp;#91;client-auth] &amp;#91;22573]: (info): MAC: aaaa.aaaa.aaaa  Client auth-interface state transition: S_AUTHIF_ADD_MOBILE_ACK_WAIT_KM -&gt; S_AUTHIF_MAB_AUTH_DONE
</span></span><span class="line"><span class="cl">2019/10/25 21:30:08.626 {wncd_x_R0-0}{1}: &amp;#91;client-orch-sm] &amp;#91;22573]: (debug): MAC: aaaa.aaaa.aaaa  Processing MAB authentication result status: 0, CO_AUTH_STATUS_SUCCESS
</span></span><span class="line"><span class="cl">2019/10/25 21:30:08.626 {wncd_x_R0-0}{1}: &amp;#91;client-orch-state] &amp;#91;22573]: (note): MAC: aaaa.aaaa.aaaa  Client state transition: S_CO_MACAUTH_IN_PROGRESS -&gt; S_CO_ASSOCIATING
</span></span><span class="line"><span class="cl">2019/10/25 21:30:08.626 {wncd_x_R0-0}{1}: &amp;#91;dot11] &amp;#91;22573]: (debug): MAC: aaaa.aaaa.aaaa  dot11 send association response. Sending association response with resp_status_code: 0 
</span></span><span class="line"><span class="cl">2019/10/25 21:30:08.627 {wncd_x_R0-0}{1}: &amp;#91;dot11] &amp;#91;22573]: (info): MAC: aaaa.aaaa.aaaa  dot11 send association response. Sending assoc response of length: 137 with resp_status_code: 0, DOT11_STATUS: DOT11_STATUS_SUCCESS
</span></span><span class="line"><span class="cl">2019/10/25 21:30:08.627 {wncd_x_R0-0}{1}: &amp;#91;dot11] &amp;#91;22573]: (note): MAC: aaaa.aaaa.aaaa  Association success. AID 1, Roaming = True, WGB = False, 11r = False, 11w = False
</span></span><span class="line"><span class="cl">2019/10/25 21:30:08.627 {wncd_x_R0-0}{1}: &amp;#91;dot11] &amp;#91;22573]: (info): MAC: aaaa.aaaa.aaaa  DOT11 state transition: S_DOT11_MAB_PENDING -&gt; S_DOT11_ASSOCIATED
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>CCIE R&amp;S Study Resources</title>
      <link>https://0x2142.com/ccie-rs-study-resources/</link>
      <pubDate>Thu, 25 Jul 2019 17:09:37 +0000</pubDate>
      <guid>https://0x2142.com/ccie-rs-study-resources/</guid>
      <description>A short list of some of the CCIE study resources I&amp;rsquo;ve been using</description>
      <content:encoded><![CDATA[<p><sub><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></sub></p>
<hr>
<p>Over the past year and a half (and still ongoing), I&rsquo;ve burned through a handful of books and other resources while working toward the CCIE. I know quite a few people have already dumped out their recommended reading lists - but I figure it&rsquo;s still worth writing out what has been working for me 🙂</p>
<p>I went ahead and studied for the written exam independently of the lab - so I&rsquo;ll be providing the resources that I used for each separately.</p>
<p>Also note: This page will be updated as I continue working toward the certification. I&rsquo;m not done yet!</p>
<h2 id="written-exam-resources">Written Exam Resources</h2>
<p>Books:</p>
<ul>
<li><a href="https://amzn.to/2S6uv7X">CCIE Routing and Switching v5.0 Official Cert Guide</a></li>
<li><a href="https://amzn.to/2XyqFup">Routing TCP/IP, Volume I</a> &amp; <a href="https://amzn.to/2XwiIG6">Volume II</a></li>
<li><a href="https://learningnetwork.cisco.com/docs/DOC-31004">CCIE/CCDE Evolving Technologies Study Guide</a></li>
</ul>
<p>Videos:</p>
<ul>
<li><a href="https://www.cbtnuggets.com/certification-playlist/Cisco/5b5b311d4c71f35645dda552">CBT Nuggets - CCIE Routing &amp; Switching</a></li>
</ul>
<h2 id="lab-exam-resources">Lab Exam Resources</h2>
<p>Books:</p>
<ul>
<li><a href="https://amzn.to/2LLtRMj">Your CCIE Lab Success Strategy: The Non-Technical Guidebook</a></li>
<li><a href="https://amzn.to/2G1QzvM">IPv6 Fundamentals: A Straightforward Approach to Understanding IPv6</a></li>
<li><a href="https://amzn.to/2S8yKjI">Cisco QOS Exam Certification Guide (IP Telephony Self Study)</a></li>
<li><a href="https://amzn.to/2G3Ud8F">IP Multicast, Volume I: Cisco IP Multicast Networking</a></li>
<li><a href="https://amzn.to/2GUAFDN">MPLS and VPN Architectures</a></li>
<li>More soon!</li>
</ul>
<p>Videos:</p>
<ul>
<li><a href="https://streaming.ine.com/p/ccie-routing-switching-pl">INE CCIE R&amp;S playlist</a></li>
</ul>
<p>Workbooks:</p>
<ul>
<li><a href="https://amzn.to/2XEdJ1v">CCIE Routing and Switching 5.1 Foundations: Bridging the Gap Between CCNP and CCIE</a></li>
<li><a href="http://labs.ine.com/workbook/toc/rs-v5-workbook">INE CCIE Routing &amp; Switching v5 workbook</a></li>
<li>More coming soon 🙂</li>
</ul>
<p>Hope these links help! What has worked for you guys? Add on in the comments below.</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>Where is all the Automation?</title>
      <link>https://0x2142.com/where-is-all-the-automation/</link>
      <pubDate>Tue, 24 Jul 2018 10:00:03 +0000</pubDate>
      <guid>https://0x2142.com/where-is-all-the-automation/</guid>
      <description>Learning Python for network automation doesn&amp;rsquo;t have to be scary. Let&amp;rsquo;s look at how to get started</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><em>The future is APIs! SD-EVERYTHING! Automation! Orchestration! Artificial Intelligence and Machine Learning!</em>
Sound familiar? It&rsquo;s all part of the messaging going around in just about everything IT-related. With as much as you keep hearing about it, you might think that it&rsquo;s all anyone is doing anymore. Yet it still just seems like not a whole lot of people are really getting into it in my area. Every vendor event I&rsquo;ve gone to this year has asked attendees the same questions: &ldquo;How many of you are leveraging the APIs in your network hardware/software?&rdquo;. And every time the same answer - maybe two or three people in a room of 40 raise their hands.</p>
<p>So where is the problem? Is all of this just marketing fluff or am I just talking to the wrong people?</p>
<p>Let&rsquo;s think about this from a typical network admin&rsquo;s perspective. Shifting from traditional CLI to automation and APIs can seem difficult or overwhelming. Let&rsquo;s say I want to automate a new VLAN deployment. <em>Oh, you&rsquo;re telling me I need to stop and learn vendor APIs… but before that I need to understand how to write scripts. But I&rsquo;ve never even programmed something before. There are dozens of languages - how do I pick one? How much fundamental programming knowledge do I really need to have before starting? I don&rsquo;t want to be a developer!</em></p>
<p>Okay, okay - just stop there for a second. No one is asking you to drop networking and write code for a living. The end goal of all this programmability stuff isn&rsquo;t to turn networkers into developers - It&rsquo;s to enable network/systems admins to be more efficient at their jobs. Why  copy/paste the same config change to 100+ devices, if you can mass-deploy the change via an API? That&rsquo;s a lot of time savings that could be used toward educating yourself on new products, planning other projects, or thinking about your ideal network design.</p>
<p>I&rsquo;ve heard a lot of the same things over the past few years:</p>
<blockquote>
<p><em>&ldquo;Programming is difficult&rdquo; or &ldquo;I don&rsquo;t know where to start&rdquo;</em></p></blockquote>
<p>Try learning Python. It&rsquo;s simple to get started and you can build from there.</p>
<blockquote>
<p><em>&ldquo;I don&rsquo;t know what an API is or how to use it&rdquo;</em></p></blockquote>
<p>Don&rsquo;t worry about that yet - start with learning the basics and APIs will make sense later.</p>
<blockquote>
<p><em>&ldquo;I&rsquo;m not a developer&rdquo;</em></p></blockquote>
<p>No one is asking you to be one! But learning the basics of scripting and automation gives you a whole new toolset to solve problems.</p>
<p>For me personally - I would never want to be a developer. I can&rsquo;t stand the thought of coming into work every day and just writing code. Some people might enjoy that, but for me it doesn&rsquo;t sound like fun. However - I enjoy writing scripts to solve problems, especially when it ends up making my job easier. I think that&rsquo;s the part where some people tend to get stuck though. A lot of automation sounds like I need to be able to develop a huge 10,000+ line application to pull data from 15 sources and aggregate it to make intelligent network changes. Ehhh&hellip; Nope, not really. But what about just a quick script that runs every 5 minutes to check an interface statistic, and email you when a particular threshold is exceeded? Realistically that could be done in less than 50-100 lines of a script and maybe 30 minutes worth of work.</p>
<p>Still not interested? That&rsquo;s okay too. Traditional networking isn&rsquo;t going away any time soon, and over time the vendors will write all of that automation for you. They will package it up in a pretty GUI and sell it off to companies that want it. In fact, this has already happening and has been for quite some time. This isn&rsquo;t a bad thing - vendors need to make money, and not all companies will have the time or skilled resources to automate all the things. However, a network admin who can write their own scripts/automation won&rsquo;t be exclusively tied to a vendor to help them - and instead they will be empowered to solve more problems themselves.</p>
<p>Where do you get started? I already wrote a bit earlier this year on a few resources for learning Python - which you can find <a href="/you-should-automate-something-this-year/">here</a>. I also wanted to point out some other great resources that are a bit more specific to using those skills for network automation:</p>
<ul>
<li>
<p><a href="https://pynet.twb-tech.com/email-signup.html">Python For Network Engineers</a> - Don&rsquo;t know anything about Python yet? Start here! This is a free course provided by Kirk Byers for anyone who is interested in using Python for network automation. Once a week you&rsquo;ll get an email with all the great free content, but it will be up to you to spend time going through it. Go sign up, and set aside an hour or two each week to practice.</p>
</li>
<li>
<p><a href="https://developer.cisco.com">Cisco DevNet</a> - There is a ton of great content here. While DevNet does offer some tutorials on basic Python fundamentals,  the real value here is examples on how to use some network APIs (NX-OS, Meraki, UCS, etc). Also - one of the best parts about DevNet is the sandboxes they offer. Want to write scripts against the FirePower Management Center, but you don&rsquo;t have one to test with? Well with DevNet you can get access to one!  Get familiar with your Python basics, then come here to see where you can start using those skills with your existing infrastructure.</p>
</li>
<li>
<p><a href="https://amzn.to/2L7EiL1">Network Programmability and Automation</a> - This is a fantastic book. Not free, but it is well worth the ~$30. Once you have a good handle on how to write some basic network automation with Python, I highly recommend picking this up. While Python is covered here, the book does a great job of introducing you to all of the other toolsets available. Curious about how Linux or Ansible fit into network automation? You can find out here - and learn about APIs and source control systems too!</p>
</li>
</ul>
<hr>
<p>So - What are you waiting for? Go get started, and see what you can accomplish. Learn the basics - and keep an open mind for opportunities to use those skills.</p>
<p>Have suggestions on where else to learn? Comment below!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Tips for Working with Vendor Support</title>
      <link>https://0x2142.com/tips-for-working-with-vendor-support/</link>
      <pubDate>Wed, 09 May 2018 12:00:24 +0000</pubDate>
      <guid>https://0x2142.com/tips-for-working-with-vendor-support/</guid>
      <description>The key to a successful support case is to work with your vendor, not against them&amp;hellip;</description>
      <content:encoded><![CDATA[<p>This post has been on my mind for a while now. I&rsquo;ve worked as a network admin for long enough, and opened more technical support cases with vendors than I want to think about. Over the years I&rsquo;ve developed my own process for how I handle those support cases in an effort to get a quick and efficient resolution. Some of this stems from starting off in a NOC, where calling vendor support was practically step 1 of any troubleshooting procedure. A lot of this is based on my own experiences or things I&rsquo;ve been taught by former co-workers.</p>
<p>On the other side of things, I&rsquo;ve worked with people over the years who haven&rsquo;t had quite the same experiences as I have. Some of them don&rsquo;t typically call vendors for support - or maybe they&rsquo;ve just never been lucky enough to be the first responder on a Severity-1/Production-down case. This has occasionally resulted in a number of vendor support cases being closed with highly unsatisfactory results. In fact, there have been times where this has been bad enough that it gives management the impression that we received bad support from the vendor - but what if it was just our own inability to work effectively with that vendor? Maybe we didn&rsquo;t push hard enough on an issue or stress the importance.</p>
<p>I feel that in a majority of cases, it shouldn&rsquo;t be difficult to get the results you want out of a vendor support case. So I&rsquo;ve put together a list of my own personal tips and guidelines for working with technical support.</p>
<h2 id="the-vendor-is-your-partner---not-the-enemy">The Vendor is your partner - Not the enemy</h2>
<p>I&rsquo;ve seen a lot of people call vendor support for help, then treat the support engineer as the bad guy. If you want to get a quick resolution, then you need to look at them as your partner. They&rsquo;re here to help you figure out your issue and get everything fixed. When you open a new case, give them a concise summary of your issue with any relevant details you think are important. Don&rsquo;t hide information, as this will just prolong finding a resolution - Give them everything they need. If you know your vendor always asks for the same diagnostics information every time you open a case, then have that information ready <em>before</em> you contact them.</p>
<p>Always remember how difficult the job of a technical support rep can be. They&rsquo;re likely sitting in a call center, just waiting for the next case. They may have in-depth knowledge of their product, but they&rsquo;re walking into your network completely blind. They won&rsquo;t know your traffic flows, or that some systems are redirected through a proxy, or that one band-aid fix that Joe put in a year ago and never documented. Your support rep is going to do the best job they can to get a handle on what your network looks like, but be prepared to guide them. How would you like to troubleshoot a completely different and unknown network every time your phone rings?</p>
<h2 id="have-realistic-expectations">Have realistic expectations</h2>
<p>Especially when you&rsquo;re new to a career in IT, it can be hard to gauge what you can and can&rsquo;t ask of your tech support rep. One of my former jobs had a policy that for every ticket with AT&amp;T we opened, we would be required to call back every hour for a status update. If there wasn&rsquo;t one, then we were supposed to demand escalation to a manager. That policy might make sense if it&rsquo;s a critical issue, but what about something that&rsquo;s not? Have realistic expectations about what your vendor can do.</p>
<p>Troubleshooting takes time. If your support engineer grabs a bunch of logs and says they&rsquo;ll need to get back to you - then you&rsquo;ll need to give them the time they need. Feel free to ask for a time estimate - but if they say they&rsquo;ll have something to you by the following day, don&rsquo;t start bugging them every hour.</p>
<p>Remember that your support engineer&rsquo;s job is to help you. If you don&rsquo;t feel like that&rsquo;s happening, you have a few options. You can ask for a case escalation or ask for the case to be reassigned. You never know the skillset of the person receiving your case, and you might get someone who isn&rsquo;t super familiar with the problem you&rsquo;ve raised. As long as there is no urgency, I will usually give the person time to work the issue - but be prepared to request a case transfer if it becomes apparent that they&rsquo;re not getting anywhere. For example, I once had a case for a web-based firewall management system. The engineer I got was very good with the GUI side of things, but wasn&rsquo;t very knowledgeable when troubleshooting took a turn toward the underlying linux system. A quick request to transfer the case to someone more experienced in this side of the system and we had the case solved within an hour. If an escalation or case transfer doesn&rsquo;t help, you can also usually reach out to your local account representative and ask them to help push the issue for you.</p>
<p>It&rsquo;s also very helpful to have an idea of your vendor&rsquo;s support policies. Have a question about how to set up a new feature? Some vendors don&rsquo;t permit you to open a case for a new configuration, and will refer you to their professional services team. On the other hand, some vendors are perfectly okay with helping you figure out how to set up something. Even better, some support teams are willing to stand by during migrations and upgrades, just in case you need their help. In my experience, if you&rsquo;re not 100% confident in your changes, then it&rsquo;s better to open a proactive case beforehand.</p>
<h2 id="be-clear-about-the-impact">Be clear about the impact</h2>
<p>If your entire datacenter is offline because of an issue, make sure that you immediately stress the importance. Again, your support engineer is jumping into your environment blind. Does this firewall performance issue impact twenty people, where it is just a minor inconvenience? Or is this issue prohibiting 50,000 customers from using the services you provide? The last thing you want is a misunderstanding of impact when it&rsquo;s a high priority issue for your business.</p>
<p>Usually when I open a high severity case, I&rsquo;ll let the engineer know: &ldquo;Just so we&rsquo;re on the same page, this issue impacts a large datacenter impacting 600+ customers. We need to get this back into a stable state as soon as possible&rdquo;. High severity cases can be stressful for both sides, and I try to be clear about the impact without making that worse.</p>
<p>On the other side of things, if there is a low severity issue - don&rsquo;t blow it out of proportions. I&rsquo;ve worked with too many engineers who open up a Sev 1/Prod-Down case for every issue, even if the issue is just a minor inconvenience. Categorize your issues appropriately when you open them - and do your best to be realistic. A slow download for three users probably doesn&rsquo;t warrant getting half a dozen TAC engineers on a conference bridge.</p>
<h2 id="in-case-of-emergency">In case of emergency</h2>
<p>Emergency situations are a completely different subject - so I want to spend a bit of time covering them separately. It&rsquo;s really important to know what constitutes a true emergency in your environment. Is it when an office (or datacenter) goes offline? Or maybe even a single extremely critical business application? Things break - so have a plan and be prepared.</p>
<p><strong>Step one</strong> - <em>Always</em> call into your vendors support line. You don&rsquo;t want to open a web/email case and wait around for a technician. This might seem obvious, but I&rsquo;ve known a lot of people who complain about the vendor&rsquo;s response on a critical issue when they opened a case via a support portal. Find the vendor&rsquo;s support number (or have it saved somewhere) and call them.</p>
<p><strong>Step two</strong> - Ask for a warm handoff. In most cases, the person answering the support line is just creating a case and routing it to the appropriate team/ticket queue. They may just give you a ticket number and tell you to expect a call back shorty. If the issue is truly critical, ask them for a warm handoff to a technician. Most vendors I have worked with have had no problem doing this, and it helps you get to troubleshooting faster.</p>
<p><strong>Step three</strong> - Clarify your issue and set expectations. You may be in a rush to get the issue fixed, but take a minute to explain your issue thoroughly and clearly. The more information you give to your support technician, the more easily they can dive into troubleshooting. And as I had mentioned earlier, be sure to set expectations and be extremely clear about the impact of your issue.</p>
<p><strong>Step four</strong> - Keep troubleshooting on track. As I&rsquo;ve stated before, you know your environment/network better than your vendor does. If they start looking at something you don&rsquo;t believe is related, you need to guide them back to the main problem.</p>
<p>In addition, if you feel after a bit that the troubleshooting isn&rsquo;t making progress - then request an escalation or a second set of eyes. There is no harm in asking for more eyes on the problem. I&rsquo;ve even had situations before where the technician said &ldquo;Well, I think we need to do X to fix it&rdquo;, and I&rsquo;ve just asked them to see what their peers think. You would rather be sure about a change, than make the issue worse.</p>
<p><strong>Step five</strong> - See the issue through to resolution. Make sure you get your environment back to a stable state before ending the call. If the technician wants to drop off and call you back after reviewing logs, let them know you&rsquo;re willing to just wait on hold. Once they&rsquo;re off the phone, it&rsquo;s easy for your technician to get dragged into another issue.</p>
<p>If the call ends with everything in a temporary state - then take follow ups on your next steps and make sure you accomplish them! Maybe you were able to restore connectivity, but need to wait for a maintenance window to make a change that is more service impacting than the original issue. Or maybe your support technician needs you to gather additional logs that they can forward to development. Whatever it ends up being, make sure you take note of it and follow through.</p>
<hr>
<p>These are just some of my own personal tips that have worked for me. Support calls with vendors don&rsquo;t always need to be a massive pain to deal with. Sure, sometimes you might have bad luck and get an inexperienced technician - but I find most issues with vendors can be solved easily enough once you know how much you can push them, and what you can and cannot ask for.</p>
<p>I hope these are useful - Let me know in the comments if you have any additional tips!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Thoughts on Cisco&#39;s 2018 Annual CyberSecurity Report</title>
      <link>https://0x2142.com/thoughts-on-ciscos-2018-annual-cybersecurity-report/</link>
      <pubDate>Wed, 21 Feb 2018 09:00:58 +0000</pubDate>
      <guid>https://0x2142.com/thoughts-on-ciscos-2018-annual-cybersecurity-report/</guid>
      <description>My thoughts on Cisco&amp;rsquo;s Annual CyberSecurity Report</description>
      <content:encoded><![CDATA[<p>When I started in networking, I never would have thought that security would be such an important part of my job. However, it has become something that I&rsquo;m involved with almost every day - tasks like applying security configurations, participating in audits, or spending a day chasing down the latest vulnerabilities. It&rsquo;s already become second nature to watch for what&rsquo;s new in the security realm, so that I&rsquo;ll be more prepared when someone asks about it.</p>
<p>Earlier today, Cisco released their <a href="https://www.cisco.com/c/en/us/products/security/security-reports.html">2018 Annual Cyber Security Report</a>. I&rsquo;ve spent some time digging through the report and thinking about what they&rsquo;ve written. It&rsquo;s interesting to read through the trends and survey results, and try to get an idea of where security efforts should be focused for the coming year.
This post is going to cover just a subset of what&rsquo;s in the complete report. I&rsquo;ll be covering the topics that I found particularly interesting, and give my own thoughts/views on them.</p>
<h2 id="the-encrypted-web-is-great-for-attackers">The Encrypted Web is Great&hellip; For Attackers</h2>
<p>Unsurprisingly, Cisco reports a growing trend in attacks and exploits that are taking advantage of encrypted transport. As a lot of large companies and Internet bodies are pushing for a 100% encrypted web, should we be surprised? Nah, it&rsquo;s the logical next step. Users want encryption because it means privacy - but that privacy also brings a method of concealing attacks.</p>
<p><img alt="image" loading="lazy" src="/content/images/2018/02/figure-1-volume-of-encrypted-traffic.png#center"></p>
<p>New exploits and malware are heavily leveraging encrypted transport to bypass all of the security we put in place to detect them. Typical defense technologies like intrusion prevention systems (IPS) are fantastic, but only when they can actually <em>read</em> the data. If a user download&rsquo;s malware through an HTTPS call, IPS won&rsquo;t usually catch it. And when that malware can now take advantage of SSL to reach back out to a command &amp; control server? Yeah, IPS might not help us there either.</p>
<p>There are technologies out there that allow enterprises to see this traffic - but maybe not enough of us are adopting it yet. A forward proxy for outbound web filtering is great. One that implements SSL decryption and inspection is even better. If your company isn&rsquo;t already decrypting outbound web traffic, then this needs to be a priority.</p>
<p>Inbound web traffic can be just as dangerous. New IIS vulnerability? Sure, let&rsquo;s grab the latest IPS signatures and &hellip; Oh wait, our IPS runs on our edge firewall, which sits in front of those web servers - which are all using SSL for encryption. That means any malicious traffic is going to slide right through our IPS undetected and land on our unpatched web servers. Get a Web Application Firewall (WAF), and let it front-end your SSL traffic. These things can be expensive and a nightmare to configure and tune properly, but right now they are one of your best options for inspecting web traffic.</p>
<h2 id="old-attacks-arent-going-away---theyre-just-getting-an-upgrade">Old Attacks Aren&rsquo;t Going Away - They&rsquo;re Just Getting an Upgrade</h2>
<p>This year&rsquo;s report highlighted that much of the older attacks are still here, and they&rsquo;re not giving up yet. Attacks via email are still present and doing more damage than you would hope. We&rsquo;re certainly getting better about implementing spam filters with reputation filtering, but attackers aren&rsquo;t giving up yet.</p>
<p>Email attacks are relying more on social engineering and targeted phishing. These messages are also utilizing SSL to encrypt the malicious links within the emails. Infected attachments are surprisingly still a big issue, with Microsoft Office and PDF files still being the worst offenders.
Just because attackers are finding new and exciting ways to hit us doesn&rsquo;t mean they&rsquo;re giving up on the tried and true methods. We still need to focus on all the standard attack vectors, like email. Implementing intelligent email/spam filters and providing user awareness training are the primary methods we have to combat this.</p>
<h2 id="the-cloud-is-secure-we-think">The Cloud is Secure! &hellip;We Think</h2>
<p>This one I found particularly fun. Out of all the companies surveyed by Cisco for this report, 57% of them said they believe the cloud offers better security. Wait - Did I misread that? More than <em>half</em>of respondents think that the cloud offers better security than their own infrastructure! This makes me wonder&hellip;</p>
<p><img alt="image" loading="lazy" src="/content/images/2018/02/figure-27-cloud-offers-security.png#center"></p>
<p>From my perspective, a cloud service provider is just another company. In most cases, they run just another network and hit a lot of the same challenges that non-cloud companies are facing. And we can only assume that cloud providers are prioritizing security and not just trying to turn a quick profit. Cloud companies have the advantage of being able to hire a dedicated security team that their customers can leverage. However, enterprises are complaining about lack of skilled security engineers, and I&rsquo;ll bet it&rsquo;s not because cloud providers are picking them all up.</p>
<p>Cloud definitely offers benefits - but this needs to be a well-calculated risk. For a smaller company without dedicated IT staff, a cloud solution would most likely offer security improvements over their own infrastructure. As companies scale, however, their security requirements do too. We need to make sure that the cloud providers we choose are also capable of adhering to those standards. Before you move to the cloud: ask questions about their security practices, get answers, and demand more information on the parts that are important to your business.</p>
<p>Another fun note from Cisco - some of the damage done by cloud providers is a simple mis-understanding of ownership. If you subscribe to a complete Software as a Service (SaaS) provider, chances are good that the provider worries about all of the critical security configurations. However, if you&rsquo;re going to a cloud provider just for infrastructure (like AWS), then you are likely responsible. In the case of AWS, you&rsquo;re being provided a server - and that&rsquo;s where Amazon&rsquo;s responsibilities end. It&rsquo;s up to the enterprise to still make sure that those servers are patched, hardened, and audited. Treat the cloud as an extension of your own infrastructure and polices, not a separate entity.</p>
<h2 id="diversifying-risks-maybe-not">Diversifying Risks? Maybe not</h2>
<p>It used to be a somewhat well-established security practice to use multiple vendors. Have a need for two sets of firewalls? Make sure you use two vendors, so that a vulnerability in one doesn&rsquo;t affect the other. Seems like sounds logic - until you have to train staff to be experts on multiple platforms, and keep up to date on all the latest patches from each vendor.</p>
<p>Cisco is finding that the more vendors a company has in their environment, the more problem we have maintaining everything. From my own experiences, I can say this is certainly a problem. In environments where I&rsquo;ve had up to four vendors for firewalls and switching, it becomes difficult to work with. It&rsquo;s hard on IT staff to maintain knowledge of configuration and best practices for each different vendor - and when a new vulnerability comes out, we end up spending way more time trying to track down each vendor&rsquo;s responses and patches.</p>
<p>It makes sense that companies who have a more tightly integrated infrastructure might have an easier time managing it. Cisco might want you to buy 100% into their ecosystem (of course), but I do think there is value in consolidating your infrastructure. One or two vendors will be much easier to establish relationships with than half a dozen of them. Your IT staff can dedicate their focus to mastering only a couple of technologies, rather than spreading themselves over a dozen different platforms. And when that new vulnerability is released? It should be much more straightforward to patch all of your systems quickly.</p>
<h2 id="theres-that-automation-thing-again">There&rsquo;s That Automation Thing Again</h2>
<p>I think we&rsquo;re finally beginning to reach a point where automation is really showing it&rsquo;s value in the security realm. A typical company is going to have so many different systems and alerts that it doesn&rsquo;t make sense for someone to manually review and act upon every one. This is where automation really begins to shine.</p>
<p>Cisco&rsquo;s report shows that more companies are relying heavily on automation. This can be used for alert response, reporting, and behavioral analytics. Especially when I keep hearing that there is a skills shortage in security, we need to take advantage of what automation can offer. This doesn&rsquo;t always have to be home-grown scripts either - there are a number of offerings already available.</p>
<p>Take a <a href="/you-should-automate-something-this-year/">second look</a> this year. Try to see where automation can fit into your infrastructure to help improve both operations and security.</p>
<hr>
<p>Thanks for reading! Just as a friendly reminder - All of the opinions stated in this post (and all others here) are 100% my own, and do not represent any vendor or employer. Since security has become more of an important part of my job, reports like this are always very interesting to read. I&rsquo;ve only covered a handful of what was in the report - just what was particularly interesting to me. If you&rsquo;re interested in reading more, check out the full report <a href="https://www.cisco.com/c/en/us/products/security/security-reports.html">here</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Autonegotiation issues on Nexus QSFP Ports</title>
      <link>https://0x2142.com/autonegotiation-issues-on-nexus-qsfp-ports/</link>
      <pubDate>Tue, 20 Feb 2018 08:00:14 +0000</pubDate>
      <guid>https://0x2142.com/autonegotiation-issues-on-nexus-qsfp-ports/</guid>
      <description>Interoperability issues between Nexus QSFP modules can cause autonegotiation to fail</description>
      <content:encoded><![CDATA[<p>Over the past two years we have made a ton of progress shifting datacenter infrastructure from 1G to 10G+. A majority of this has been through a vendor migration back to Cisco for switching - and specifically using the Nexus 9372 line. These boxes come with 48 ports of 1G/10G SFP+ and another 6 QSFP ports that hit 40G.</p>
<p>Late last year we placed an order to expand our 10G+ coverage in one of our larger datacenters. After meeting with our local Cisco reps and talking through options, we settled on a pair of Nexus 93180YC-EX switches. The new toys offer additional flexibility, by providing 48 SFP+ ports capable of 1G/10G/25G and the 6 QSFP ports are 40/100G.</p>
<p>A week or two ago we worked during a planned maintenance window to try and bring the new 93180s online. The new switches are just directly connected back to the 9372s using four QSFP-40G-CR4 cables. The time comes, we turn up the ports - and they don&rsquo;t come up. We know the cable types definitely work, since we&rsquo;re using them for all of our current interconnects between the 9372s. Unfortunately, due to tight timelines on <a href="/how-does-maintenance-scheduling-affect-your-network/">maintenance windows</a> - we have to turn down the ports and move on to other task.</p>
<p>So we go down the normal line of troubleshooting. Reseat cables - still nothing. Remove port-channel/VPC configurations - nothing. Test the QSFP cables by cabling in between just the new 93180s - yeah, ports come up and the cables are good. One of my teammates, who is running with this task, is almost at the point of opening up a support case with TAC. I double checked the switch port configurations - but everything looks good. My first thought was that maybe there is a speed/autonegotiation issue - since the QSFP ports on the 9372s are fixed 40G, while the 93180s are 40/100G.</p>
<p>We scheduled another quick no-downtime maintenance window to test out the theory. Each of the ports on both sides of the connection gets the following configuration changes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Switch(config)# interface x/x
</span></span><span class="line"><span class="cl">Switch(config-if)# no negotiate auto
</span></span><span class="line"><span class="cl">Switch(config-if)# duplex full
</span></span><span class="line"><span class="cl">Switch(config-if)# speed 40000
</span></span></code></pre></div><p>The time comes - and sure enough the ports come online.</p>
<hr>
<p>Just wanted to throw this out there in case anyone else runs across the same problem. The fix is surely easy enough, but you don&rsquo;t always think of autonegotiation issues - especially in such a simplistic configuration as this.</p>
<p>I also wanted to say thanks to the great people in the <a href="https://communities.cisco.com/groups/cisco-champions">#CiscoChampions</a> DataCenter group. I was able to run the problem through them, and they suggested the same potential root cause. It&rsquo;s always great to have a second opinion to provide some confidence, especially when there are strict time constraints for maintenance.</p>
]]></content:encoded>
    </item>
    <item>
      <title>L2 Basics: Configuring an EtherChannel</title>
      <link>https://0x2142.com/l2-basics-configuring-an-etherchannel/</link>
      <pubDate>Tue, 30 Jan 2018 10:00:46 +0000</pubDate>
      <guid>https://0x2142.com/l2-basics-configuring-an-etherchannel/</guid>
      <description>How to configure a basic etherchannel on Cisco devices</description>
      <content:encoded><![CDATA[<p>Today we&rsquo;re going to take a look at how to configure an etherchannel between two Cisco Switches.</p>
<p>What is an etherchannel? It&rsquo;s a way of taking multiple independent links and bundling them together, so that they appear as one logical connection between two devices. Etherchannels are commonly used between two switches, or between a switch and a host - which allows for both additional bandwidth and fault tolerance/redundancy. In the example today, we&rsquo;ll be using an etherchannel protocol called Link Aggregation Control Protocol (LACP). LACP is an IEEE standard (802.3ad).</p>
<p>You might be thinking &ldquo;Wait, wouldn&rsquo;t multiple links cause a loop? Or trigger <a href="/l2-basics-spanning-tree-protocol/">Spanning-tree</a> to block ports?&rdquo;. Not in this case! Etherchannel technologies work around those problems by creating a single logical interface for spanning-tree to worry about. The etherchannel protocol itself worries about loop prevention in between the two devices, so we get multiple ports of non-blocking bandwidth.</p>
<p>For everything we cover in this example, we&rsquo;ll be using the following topology:</p>
<p><img alt="image" loading="lazy" src="/content/images/2018/01/lacp.png#center"></p>
<p>So we have two switches, which are connected together via Eth0/0 and Eth0/1. Each switch has three VLANs configured - 10, 20, and 30.</p>
<h2 id="configuring-an-etherchannel">Configuring an Etherchannel</h2>
<p>I&rsquo;ll only be showing the configuration from the perspective of 0x2142-SW1 - but all configuration is replicated on 0x2142-SW2.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">! We&#39;ll use the interface range command to apply the etherchannel configuration to
</span></span><span class="line"><span class="cl">! both Eth0/0 and Eth0/1 at the same time:
</span></span><span class="line"><span class="cl">0x2142-SW1(config)#int range Eth0/0 - 1
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">! We specify which etherchannel protocol to use by configuring &#39;channel-protocol&#39;
</span></span><span class="line"><span class="cl">! PAgP is a Cisco Proprietary protocol, but we&#39;ll be using LACP for this example:
</span></span><span class="line"><span class="cl">0x2142-SW1(config-if-range)#channel-protocol ?
</span></span><span class="line"><span class="cl">  lacp  Prepare interface for LACP protocol
</span></span><span class="line"><span class="cl">  pagp  Prepare interface for PAgP protocol
</span></span><span class="line"><span class="cl">0x2142-SW1(config-if-range)#channel-protocol lacp
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">! Next we need to specify a channel-group and mode:
</span></span><span class="line"><span class="cl">0x2142-SW1(config-if-range)#channel-group 1 mode ?
</span></span><span class="line"><span class="cl">  active     Enable LACP unconditionally
</span></span><span class="line"><span class="cl">  auto       Enable PAgP only if a PAgP device is detected
</span></span><span class="line"><span class="cl">  desirable  Enable PAgP unconditionally
</span></span><span class="line"><span class="cl">  on         Enable Etherchannel only
</span></span><span class="line"><span class="cl">  passive    Enable LACP only if a LACP device is detected
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">0x2142-SW1(config-if-range)#channel-group 1 mode active
</span></span><span class="line"><span class="cl">Creating a port-channel interface Port-channel 1
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">0x2142-SW1(config-if-range)#
</span></span><span class="line"><span class="cl">*Jan 26 01:03:04.532: %LINEPROTO-5-UPDOWN: Line protocol on Interface Port-channel1, changed state to up
</span></span></code></pre></div><p>Let&rsquo;s talk through a few notes about the above configuration. In order to enable etherchannel, we only need to configure two commands: <code>channel-protocol</code> and <code>channel-group</code>. The <code>channel-protocol</code> command tells the switch which etherchannel protocol to use for negotiation (LACP in this case). The <code>channel-group</code> command provides two necessary components: the group number and mode. The group number is just a device-local identifier for which group to add these ports to. When we specified group 1, the switch adds both Eth0/0 and Eth0/1 into the new logical interface Port-Channel 1.</p>
<p>The etherchannel mode is also important. The two primary options we want to look at for LACP are active and passive. Active tells the switch to preemptively send out LACP negotiation packets. In this case, the switch really wants the ports to become a bundle and will ask it&rsquo;s partner device for an etherchannel to be formed. Passive mode tells our switch to only negotiate if another device wants to. In this mode, our switch won&rsquo;t send out any etherchannel negotiation packets unless its partner device does so first.</p>
<p>Generally speaking, the most common configuration is to set the mode on both devices to active. This ensures that both devices actively participate in trying to establish an etherchannel. Placing one device in active and one in passive will work as well. However, if both devices are placed into passive mode, an etherchannel will never form.</p>
<h2 id="validation">Validation</h2>
<p>So how do we validate that the etherchannel has formed correctly? One way is using the <code>show etherchannel summary</code> command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0x2142-SW1#show etherchannel summary
</span></span><span class="line"><span class="cl">Flags:  D - down        P - bundled in port-channel
</span></span><span class="line"><span class="cl">        I - stand-alone s - suspended
</span></span><span class="line"><span class="cl">        H - Hot-standby (LACP only)
</span></span><span class="line"><span class="cl">        R - Layer3      S - Layer2
</span></span><span class="line"><span class="cl">        U - in use      N - not in use, no aggregation
</span></span><span class="line"><span class="cl">        f - failed to allocate aggregator
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        M - not in use, minimum links not met
</span></span><span class="line"><span class="cl">        m - not in use, port not aggregated due to minimum links not met
</span></span><span class="line"><span class="cl">        u - unsuitable for bundling
</span></span><span class="line"><span class="cl">        w - waiting to be aggregated
</span></span><span class="line"><span class="cl">        d - default port
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        A - formed by Auto LAG
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Number of channel-groups in use: 1
</span></span><span class="line"><span class="cl">Number of aggregators:           1
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Group  Port-channel  Protocol    Ports
</span></span><span class="line"><span class="cl">------+-------------+-----------+-----------------------------------------------
</span></span><span class="line"><span class="cl">1      Po1(SU)         LACP      Et0/0(P)    Et0/1(P)
</span></span></code></pre></div><p>From the output above, we see that there is one group configured with the group ID of 1. It shows that both Eth0/0 and Eth0/1 have been added into the Port-channel 1 interface. The (SU) next to the Port-channel interface indicate that the etherchannel is up (U) and configured for layer 2 (S).
I mentioned earlier that spanning-tree only worries about the port-channel interface, not the individual member ports. We can also check that out by using <code>the show spanning-tree</code> command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0x2142-SW1#sh spanning-tree vlan 20
</span></span><span class="line"><span class="cl">VLAN0020
</span></span><span class="line"><span class="cl">  Spanning tree enabled protocol rstp
</span></span><span class="line"><span class="cl">  Root ID    Priority    32788
</span></span><span class="line"><span class="cl">             Address     aabb.cc00.1000
</span></span><span class="line"><span class="cl">             This bridge is the root
</span></span><span class="line"><span class="cl">             Hello Time   2 sec  Max Age 20 sec  Forward Delay 15 sec
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  Bridge ID  Priority    32788  (priority 32768 sys-id-ext 20)
</span></span><span class="line"><span class="cl">             Address     aabb.cc00.1000
</span></span><span class="line"><span class="cl">             Hello Time   2 sec  Max Age 20 sec  Forward Delay 15 sec
</span></span><span class="line"><span class="cl">             Aging Time  300 sec
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Interface           Role Sts Cost      Prio.Nbr Type
</span></span><span class="line"><span class="cl">------------------- ---- --- --------- -------- --------------------------------
</span></span><span class="line"><span class="cl">Et0/2               Desg FWD 100       128.3    Shr
</span></span><span class="line"><span class="cl">Et0/3               Desg FWD 100       128.4    Shr
</span></span><span class="line"><span class="cl">&lt;-- Output omitted --&gt;
</span></span><span class="line"><span class="cl">Po1                 Desg FWD 56        128.65   Shr
</span></span></code></pre></div><h2 id="making-configuration-changes-to-an-etherchannel">Making Configuration Changes to an Etherchannel</h2>
<p>Now that we have a working etherchannel - We have a few things that need special attention. The individual port configurations, Eth0/0 and Eth0/1 in this case, need to match at all times! Port configuration mis-matches are going to be an easy way to inadvertently bring down the port-channel. The good thing is that we now have a convenient Port-Channel interface which we can use for configuration. This logical port will replicate any configuration changes to all member ports.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">! Let&#39;s jump into our Port-Channel 1 interface and configure a trunk for VLAN 20
</span></span><span class="line"><span class="cl">0x2142-SW1(config)#int po1
</span></span><span class="line"><span class="cl">0x2142-SW1(config-if)#switchport mode trunk
</span></span><span class="line"><span class="cl">0x2142-SW1(config-if)#switchport trunk allowed vlan 20
</span></span><span class="line"><span class="cl">! Now we can check the individual port configs:
</span></span><span class="line"><span class="cl">0x2142-SW1(config-if)#do sh run int e0/0
</span></span><span class="line"><span class="cl">Building configuration...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Current configuration : 176 bytes
</span></span><span class="line"><span class="cl">!
</span></span><span class="line"><span class="cl">interface Ethernet0/0
</span></span><span class="line"><span class="cl"> switchport trunk allowed vlan 20
</span></span><span class="line"><span class="cl"> switchport mode trunk
</span></span><span class="line"><span class="cl"> channel-protocol lacp
</span></span><span class="line"><span class="cl"> channel-group 1 mode active
</span></span><span class="line"><span class="cl">end
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">0x2142-SW1(config-if)#do sh run int e0/1
</span></span><span class="line"><span class="cl">Building configuration...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Current configuration : 176 bytes
</span></span><span class="line"><span class="cl">!
</span></span><span class="line"><span class="cl">interface Ethernet0/1
</span></span><span class="line"><span class="cl"> switchport trunk allowed vlan 20
</span></span><span class="line"><span class="cl"> switchport mode trunk
</span></span><span class="line"><span class="cl"> channel-protocol lacp
</span></span><span class="line"><span class="cl"> channel-group 1 mode active
</span></span><span class="line"><span class="cl">end
</span></span></code></pre></div><p>Easy enough, right? The configuration changes for the trunk are now on both Eth0/0 and Eth0/1.</p>
<h2 id="troubleshooting-etherchannels">Troubleshooting Etherchannels</h2>
<p>There is always a possibility that something goes wrong - so let&rsquo;s take a quick look at some common problems and how to fix them.</p>
<p>Remember how I said that the member port configurations had to match? Here&rsquo;s what happens if we make a configuration change on only one of the two member ports:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">0x2142-SW1(config)#int eth0/1
</span></span><span class="line"><span class="cl">0x2142-SW1(config-if)#switchport trunk allowed vlan 30
</span></span><span class="line"><span class="cl">0x2142-SW1(config-if)#
</span></span><span class="line"><span class="cl">*Jan 28 20:43:55.458: %EC-5-CANNOT_BUNDLE2: Et0/1 is not compatible with Et0/0 and will be suspended (vlan mask is different)
</span></span></code></pre></div><p>Eth0/1 immediately gets put into a suspended state, and is no longer active in the port-channel interface. In this case the switch gives us a good hint as to what&rsquo;s wrong - vlan mask is different. Error messages will vary slightly, but a suspended port is easy to fix by comparing individual port configurations and fixing the mismatch.</p>
<p>Here&rsquo;s another one:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">*Jan 28 21:06:07.346: %EC-5-L3DONTBNDL2: Et0/0 suspended: LACP currently not enabled on the remote port.
</span></span><span class="line"><span class="cl">*Jan 28 21:06:08.009: %EC-5-L3DONTBNDL2: Et0/1 suspended: LACP currently not enabled on the remote port.
</span></span></code></pre></div><p>This error message can mean a few things - the common one being exactly what it states! Check both sides of the connection, and ensure that LACP is configured on each device. This error message can also occur on certain mismatches - like if one side is running as a Layer 2 etherchannel, but the other side is running as Layer 3.</p>
<p>One more:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Jan 28 20:83:55.458 %ETHPORT-5-IF_DOWN_PORT_CHANNEL_MEMBERS_DOWN: Interface port-channel1 is down (No operational members)
</span></span></code></pre></div><p>The above message is also somewhat self-explanatory. In this case, the switch is unable to bring up the port-channel interface, because none of the underlying member ports are coming online. Troubleshoot what might be wrong with those ports first, then the port-channel should come up.</p>
<hr>
<p>Hope this was useful! In a later post, we&rsquo;ll dig into more configuration and considerations - like packet hashing, layer 3 etherchannels, and how packets are weighted between interfaces.</p>
<p>Questions? Drop them in the comments below!</p>
]]></content:encoded>
    </item>
    <item>
      <title>2018 Cisco Champions</title>
      <link>https://0x2142.com/2018-cisco-champions/</link>
      <pubDate>Fri, 19 Jan 2018 16:11:40 +0000</pubDate>
      <guid>https://0x2142.com/2018-cisco-champions/</guid>
      <description>#CiscoChampion</description>
      <content:encoded><![CDATA[<p>I received an email late yesterday afternoon - to my surprise I was invited to join the <a href="https://communities.cisco.com/groups/cisco-champions">Cisco Champions</a> program for 2018! I applied back in November, but I never would have thought I would actually get selected.</p>
<p><img alt="image" loading="lazy" src="/content/images/2018/01/2018-Cisco-Champion.jpg#center"></p>
<p>I&rsquo;m very excited to join the wonderful group of existing Cisco Champions, a few of which who I&rsquo;ve bugged on Twitter already. I&rsquo;ve heard a lot of great things about the program, but since it&rsquo;s my first year I&rsquo;m interested to experience it for myself. Overall I&rsquo;m happy for the opportunity and I&rsquo;m looking forward to what it brings!</p>
<p>Thanks for being here - It&rsquo;s going to be an exciting 2018!</p>
]]></content:encoded>
    </item>
    <item>
      <title>One Year Later</title>
      <link>https://0x2142.com/one-year-later/</link>
      <pubDate>Tue, 02 Jan 2018 08:51:47 +0000</pubDate>
      <guid>https://0x2142.com/one-year-later/</guid>
      <description>Some thoughts on the past year, and goals for the year to come</description>
      <content:encoded><![CDATA[<p>2017 is over! Now we&rsquo;re on to whatever 2018 may bring. The past year has been very interesting for me. For one thing, it was the first full year of this blog which started in <a href="/a-new-start/">December of 2016</a>. While I didn&rsquo;t quite accomplish everything here that I had hoped for, I still managed to do a lot more than I realistically expected.</p>
<p>One of the things I&rsquo;ve had problems with in the past is keeping a blog updated. Usually I would start, write an entry or two, then completely forget about it. I never thought I had good enough content to warrant sharing, or I was trying to keep to too narrow a topic. So when I started this blog, I said that I was going to focus on networking but leave it a bit more open-ended. I also wanted to try sharing some more generalized IT experience and career advice. I started off with a list of topics that I wanted to write about, and even began pre-writing a few of them so that I had a bit of content lined up ahead of time.</p>
<p>Even though I told myself originally that I was only going to post something whenever I had something good to share, I still ended up setting myself a goal of writing one thing a week. For a while this actually worked out, because I was forcing myself to think about it more often - but eventually I ran out of immediate ideas. I had to remind myself that it was more important for me to write/post content that was actually worth reading, not just having something available on a weekly basis. Even so, I&rsquo;ve managed to post 44 items since I started, 40 of which were in 2017 - Much better than I had actually anticipated.</p>
<p>So here is to 2018 - I&rsquo;m not going to try and set any strict goals for myself in terms of posting content (or at least I&rsquo;ll tell myself that now). However, I&rsquo;m also going to try and work on getting better at putting up content. I spend too much time waiting for that &lsquo;great thing&rsquo; to write about, and not enough time on just writing something that might not be particularly fantastic - even though it might still benefit someone. I feel like I have a lot to share, and not everyone is an expert. Continuing to think that much of my content &lsquo;isn&rsquo;t good enough to post&rsquo; is just holding me back. I&rsquo;m going to try and be better this year about this - and not keep waiting for only the &lsquo;great things&rsquo; to share.</p>
<p>The other big thing I&rsquo;ll be focusing on this year is studying for the CCIE R&amp;S, which I <a href="/my-2018-goal-ccie-rs/">wrote about</a> in October. I bought a few books and found some training videos, which I&rsquo;ve been slowly working though&hellip; and when I say slowly, I mean probably much slower than I should be. Now that the holidays are over and it&rsquo;s a new year, I&rsquo;ll be pushing myself a more to actually make progress. My current tentative goal for attempting the written exam is June - so I&rsquo;m hopeful that I&rsquo;ll be able to make it work.</p>
<p>The blog has been fun so far, and I&rsquo;ve done a bit more than I thought I would with it. However, there was one thing over the past year that I wasn&rsquo;t really expecting at all - getting to talk with a bunch of other people who are interested in networking/IT. I&rsquo;ve mostly been on Twitter, and more recently on Reddit&rsquo;s /r/networking and /r/cisco. There have been a ton of people I&rsquo;ve gotten to talk to, get opinions from, or even a few people that I&rsquo;ve been able to help out with some of their problems. A large portion of my career has been limited to working with just a small team of people, few which actually have much interest in networking. I&rsquo;ve really enjoyed the experiences over the past year, and I&rsquo;m really looking forward to what else might come. If you&rsquo;re one of the people I&rsquo;ve interacted with over the past year, thank you!</p>
<hr>
<p>A new year comes with new challenges, problems, and complaints - but it also comes with new accomplishments and new things to look forward to. I hope that all of you reading this are able to set new goals for the year and pass your expectations!</p>
]]></content:encoded>
    </item>
    <item>
      <title>How to Improve: Stop Doing, Start Understanding</title>
      <link>https://0x2142.com/how-to-improve-stop-doing-start-understanding/</link>
      <pubDate>Tue, 28 Nov 2017 08:07:54 +0000</pubDate>
      <guid>https://0x2142.com/how-to-improve-stop-doing-start-understanding/</guid>
      <description>The best way to truly understand a technology is to dig deeper than surface-level configurations</description>
      <content:encoded><![CDATA[<p>There is a key to being successful at just about any IT job: Stop just doing work, and start understanding what you&rsquo;re doing. Might seem like an odd thing to say right? But this is something that I have seen confuse engineers at earlier points in their careers.</p>
<p>In a lot of jobs, the initial training you receive is fairly straightforward. You are usually taught how to respond to a task by following a series of steps to get an intended result. Training like this is great - It helps to achieve consistency and efficiency. You bring in any new person and give them the exact same troubleshooting steps, implementation steps, and/or validation steps - and you&rsquo;re likely to get a similar result every time.</p>
<p>This is the point where I have seen far too many people stop though. They are happy with doing their job, and don&rsquo;t necessarily want to progress their career or maybe they don&rsquo;t know how. These engineers will continue to produce decent work at the quality that they were taught at. Even for those who try and progress further (maybe through certifications or otherwise), there is a difference between learning new technologies/concepts and truly understanding them. For some people out there, having this basic level of skill is all they really want - and if that&rsquo;s their goal in life, then this type task-based knowledge is perfect. But if you really want to master the domain of technology that you are interested in, then you need to put forth the time and effort into gaining that understanding.</p>
<p>For me, a true understanding of a technology means that you&rsquo;re able to speak confidently about how something works, abstract concepts to apply to similar products, and mentally walk though how the technology might handle a given situation.</p>
<p>Let me provide an example or two that might help to frame this a bit better. Given a particular network, an engineer might know that for traffic to get from point A to point B, it travels through two firewalls. Every time there is a new request to permit a new traffic flow, that engineer knows that they must make a configuration change to one or both of those firewalls to allow that traffic through. However, to this engineer, the inner workings of that firewall are a complete mystery. The firewalls are complete black boxes which take in traffic through one interface and spit it out another. So when there is a technical issue within the firewall appliance, they may be extremely limited in their troubleshooting abilities - and they may have no choice but to call the vendor for support.</p>
<p>Another engineer who has a deeper understanding of how firewalls work might see the problem differently. This engineer knows that for every packet received, the firewall follows a specific flow of processing. That flow could include any number of things, including NAT, routing, firewalling, IPS, VPN, etc. This engineer knows which order those things get processed and what effects those processes can have on the traffic. So when we have a technical issue with this firewall appliance, this engineer may be able to mentally walk though the packet flow/processing and determine where the problem may be - sometimes without even looking further than general log files.</p>
<p>Another example is something that I see quite often. An engineer is asked to implement something - let&rsquo;s say a new port configuration for a server. They follow their known process for implementing this change, but something doesn&rsquo;t quite work right. So they change settings or maybe delete the entire port configuration and start over - but eventually they get it working. They don&rsquo;t know why it didn&rsquo;t work the first time, or what caused it to work the second time - but it works now, so they aren&rsquo;t concerned with it. However, it&rsquo;s possible this engineer ends up running into this same problem more than once. The ideal step here would be to step back and look at what was different between the original configuration and the working configuration. Maybe there is an additional command in the original configuration which seems suspect - a quick search of the internet could turn up an explanation behind why that command was preventing the port from working as expected. After that research, that engineer would not only know why their configuration didn&rsquo;t work - but now they know what that command actually does, which could be beneficial in a future scenario.</p>
<p>As I briefly mentioned earlier, not all IT admins or engineers are concerned with gaining a significant level of understanding. There are those who want to come to work, get their job done, then go home to their families - and there is absolutely nothing wrong with that. For me personally, I can&rsquo;t handle running into a problem and not knowing exactly what the cause was. An issue that &ldquo;fixes itself&rdquo; is never an acceptable answer to me, because if something caused the problem once then it can certainly happen again. I don&rsquo;t enjoy having to blindly configure an option on a system without knowing what&rsquo;s going on in the background. Some people might call me crazy, but this seems to be a skill/trait shared by many higher-level engineers I have worked with.
So how do you get to a point where you really understand a system? For me, it&rsquo;s been a lot of playing in labs, reading vendor documentation, and not settling until I feel like I can speak confidently to how something works. I never feel truly comfortable in a new company until I can mentally walk through every device a packet touches from source to destination - and know which devices configs/routes may have an impact on that flow. Any time there is a problem with something, I spend time digging into it until I know what caused it - even if the problem is only momentary and goes away. Not only do I then understand why the problem happened, but I also learn how to quickly identify similar issues again.</p>
<p>Especially if you&rsquo;re still in the beginning stages of your career, I can&rsquo;t stress enough how important it is to understand the technologies you&rsquo;re responsible for. Take the extra time and study it, play with it, break it and fix it again. Know how things work and what their behaviors are under different conditions. Don&rsquo;t settle for &ldquo;It just works because it does&rdquo;. One of the key skills I&rsquo;ve seen in engineers who truly understand their domain of technology, is the ability to abstract concepts to apply to other systems. Someone who has a deep understanding of routing and switching technologies might prefer to work with a certain vendor, but given any router/switch they can make it work.</p>
<p>Have you worked with anyone who you think has a great understanding of what they do? What other skills or traits do they display that makes them successful? Comment below!</p>
]]></content:encoded>
    </item>
    <item>
      <title>What&#39;s Going Out of Your Network?</title>
      <link>https://0x2142.com/whats-going-out-of-your-network/</link>
      <pubDate>Tue, 21 Nov 2017 08:00:53 +0000</pubDate>
      <guid>https://0x2142.com/whats-going-out-of-your-network/</guid>
      <description>Ever consider enabling firewall filtering for outgoing traffic from your network? Let&amp;rsquo;s look at why that could be interesting&amp;hellip;</description>
      <content:encoded><![CDATA[<p>Over this past weekend I purchased a few upgrades to my home network/lab. One of which was upgrading my older Ubiquiti 802.11n wireless access point to the newer 802.11ac model they have out. The other purchase was a new external firewall. I had previously been running on a Cisco ASA5505, but the device is older and doesn&rsquo;t support some of the newer features I would like to play with. In addition, in my current job I no longer support Cisco firewalls. So I bought a <a href="https://www.amazon.com/gp/product/B01ICEO2U4/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;tag=0x2142-20&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01ICEO2U4&amp;linkId=35fbe8300af4e5d1e26e7a860782b3ca">Juniper SRX300</a> - which should allow me to play with some new features I want, plus it can be a playground for testing things I want to do at work.</p>
<p>Anyways - after I cut over to my new firewall, I&rsquo;ve been digging through logs to make sure that I didn&rsquo;t miss anything. I have all of my device/lab logs going into an instance of Splunk Light (their free product). It makes it easy to collect and search through logs, and it&rsquo;s extremely easy to set up and use. A few quick queries and I came across one or two minor things that needed to be tweaked on my firewall - but I also saw some traffic that I wasn&rsquo;t sure about.</p>
<p>So that brings me to my question of the day: Do you know what&rsquo;s going out of your network?</p>
<p>A lot of people I know only use firewalls to block inbound access, both in homes and businesses. For homes it&rsquo;s more understandable since most average people aren&rsquo;t network admins. However, it still surprises me how many businesses are willing to add a &lsquo;permit any any&rsquo; out to the internet. Yes, I block all traffic by default through my home firewall, both inbound and outbound. Yes, it&rsquo;s a bit of a pain sometimes when something isn&rsquo;t quite working right - but it&rsquo;s usually a quick ACL change, and overall I would rather take the minor inconvenience for the security gains.</p>
<p>When I originally built the firewall policy for my network, I started off simple. I know we need DNS, HTTP, and HTTPS outbound - easy enough, right? Then I started watching logs for blocked traffic and trying to decipher what else was trying to communicate outbound using another port. Some things were very easy to determine - TCP 5228 out to a Google owned IP? Yep that&rsquo;s actually a known thing - a lot of Google services, like Chrome, will use this. Some other things were harder to figure out - like game consoles which use a very wide range of non-standard ports. Many of these weren&rsquo;t really documented well by the console manufacturer, and meant that I spent a while between browsing forums and some trial and error.</p>
<p>This really gets interesting when you start digging past the stuff you know about. What about a PC in my home network that is trying (and getting blocked) to reach a few random IPs in Korea and Russia over a bunch of non-standard TCP ports? Yeah that doesn&rsquo;t make me feel comfortable. Could it be a legitimate application, or is it malware? A few quick searches on the internet don&rsquo;t turn up anything immediately helpful. For the time being, I&rsquo;ll keep stuff like this blocked until I have time to spin up some packet captures to see what this traffic is actually doing.</p>
<p>For a business I feel like this type of thing is even more important than just what I&rsquo;m doing at home. You certainly don&rsquo;t want end users (or servers) possibly running strange applications, which might be transferring data to some unknown external party. It seems like larger companies seem to have a better handle on restricting outbound access than most smaller companies, who likely don&rsquo;t have the time or see the value. However, I&rsquo;ve also worked with a few larger organizations who still permit all user and server traffic out to the internet with no filtering in place.</p>
<p>If you&rsquo;re not already blocking outbound traffic - Get some good logging in place. Use something like Splunk Light and start collecting firewall logs for everything going out of your environment. Start with the basics - create a list of the software/ports you know you&rsquo;ll need to open. After a few weeks, start digging through the logs to figure out what else might need to be added to your list. Once you feel comfortable that you&rsquo;ve compiled a sufficient base ruleset, schedule a time to make the change and put it in place. Start blocking the unknown traffic - and only permit when necessary.</p>
<p>How do you have your firewalls configured today? Do you permit everything or are you very restrictive? Comment below - I&rsquo;m curious to see what other people are doing.</p>
]]></content:encoded>
    </item>
    <item>
      <title>L2 Basics: Spanning-Tree Protocol</title>
      <link>https://0x2142.com/l2-basics-spanning-tree-protocol/</link>
      <pubDate>Tue, 14 Nov 2017 08:00:22 +0000</pubDate>
      <guid>https://0x2142.com/l2-basics-spanning-tree-protocol/</guid>
      <description>The fundamentals of how Spanning-Tree Protocol works</description>
      <content:encoded><![CDATA[<p>Spanning-tree protocol (STP) is one of those network technologies that is easy to forget about. It exists in the background of almost every network, and for the most part it does it&rsquo;s job without any issues. However, there is still a huge benefit to understanding what STP does and how it works - because it&rsquo;s default behaviors might not the the best for every network.</p>
<p>I&rsquo;ve been making progress going through my CCIE books, and the earlier sections are focusing on layer 1 and layer 2 technologies. A lot of this is review from CCNP studies, but with STP the book starts to get into additional detail on the inner workings of the protocol - which I&rsquo;m finding to be quite fascinating. It seems like in many of the companies that I&rsquo;ve worked I&rsquo;ve found that a majority of the IT staff (whether sysadmins or network admins) don&rsquo;t really have a good handle on how STP works and why it needs to be tuned. So this post is meant to cover spanning-tree at a very high level, and I&rsquo;ll include some examples from issues I&rsquo;ve seen in the past.</p>
<h2 id="so-what-is-spanning-tree-protocol-anyways">So what is spanning-tree protocol anyways?</h2>
<p>At it&rsquo;s very basic level, STP is a communications protocol used between switches to allow them to identify redundant paths in the network. The goal of STP is to figure out what is the most efficient L2 path through the network, then block all other paths to prevent loops. The best way I&rsquo;ve heard STP explained is that it&rsquo;s essentially a routing protocol for layer 2. Rather than routers communicating and exchanging routes to determine the best path through a network, all of the switches will talk to determine the best (loop-free) layer 2 path.</p>
<h2 id="stp-determines-the-best-layer-2-path---but-the-best-path-to-what">STP determines the best layer 2 path - but the best path to what?</h2>
<p>When configuring a standard routing protocol (like EIGRP or OSPF), you might have a node that advertises a route for 10.10.10.0/24. All other routers in the network are going to select a best path to the router who originates this advertisement - but how does something like this work when we&rsquo;re talking about layer 2?</p>
<p>Spanning-tree relies on the concept of having a single root bridge of each network. At the beginning of a spanning-tree process, all switches will hold a quick election to determine who the root bridge is - then each switch will figure out what it&rsquo;s own best path is to that device. The switch that ultimately becomes the root bridge will be based on the priority set by the administrator - but by default all switches are pre-configured with the same priority. In a tie, the switch with the lowest MAC address will win and become the root bridge.</p>
<p>What does that actually mean? More or less, one switch gets put in charge of defining the best path through the network. All other switches examine all of their redundant paths to the primary switch,  then figure out which of those paths are more preferable than the others. An important note here, is that the &ldquo;best path&rdquo; selected is all from the specific viewpoint of whichever switch takes charge.</p>
<p>For an example, let&rsquo;s use the following topology:</p>
<p><img alt="image" loading="lazy" src="/content/images/2017/11/1-default.png#center"></p>
<p>In this example, we have five switches and a firewall - which are used to provide connectivity to two network segments (NET1 and NET2). For each of the two network segments, there are a number of different paths that traffic could take to reach the firewall. Without spanning tree, NET1 might send traffic to SW4, which in turn would forward it to both SW2 and SW3. This sounds like a good thing, since we would use all available paths to try and reach the firewall - but in reality this can cause other problems like the firewall receiving packets out of order.</p>
<p>So for the example above, let&rsquo;s assume that SW1 becomes our root bridge. SW1 is now in charge of determining what the best path through the network is. It does this by sending out messages on all ports connected to other switches, called Bridge Protocol Data Units (BPDU). In this message, SW1 asserts it&rsquo;s role as the root bridge - and provides some information for other switches to use for path selection. Each switch will examine the message from SW1 to determine which of it&rsquo;s uplinks is the most efficient path to SW1. Once each switch does this, it will forward on the message to downstream switches - this time adding in some of it&rsquo;s own information (or path cost).</p>
<p>After all that is complete, we might be left with the following path below:</p>
<p><img alt="image" loading="lazy" src="/content/images/2017/11/2-ideal.png#center"></p>
<p>The green lines above show the final path that was selected. For NET1 to reach the firewall, it would use SW4, then SW2, then up to SW1. For NET2, it would use SW5 &gt; SW2 &gt; SW1. This leaves the orange links unused. In fact, spanning-tree will place these links into a blocking state. The switches might still listen on those links, just in case their neighbor starts advertising a better path - but they will not forward any data traffic on these connections. In the case of SW2 suddenly failing, SW4 and SW5 would still be aware of their connections through SW3 - and after a brief period would begin using those links to reach the firewall.</p>
<p>This is a very simplistic explanation, and there is a lot more in the background that actually happens during spanning-tree operation. There are a number of different STP standards that a switch can run, each with their own options for configuration and tuning. There are also methods of providing a loop-free path while still utilizing some redundant paths. I plan to cover some more detail on these topics in later posts.</p>
<h2 id="so-why-should-i-care-about-stp">So why should I care about STP?</h2>
<p>Remember that part earlier when I said that if STP priority is not configured, then switch with the lowest MAC becomes the root bridge? Well as it turns out, MAC addresses are the hardware addresses configured by the manufacturer - and these addresses increment as they produce new devices. So the lower MAC addresses are typically going to be the oldest equipment in your network. Unfortunately, this can have a dramatic effect on your network traffic if you&rsquo;re not paying attention to STP.</p>
<p>From the earlier example, what happened if SW4 became the root bridge? Maybe this was an old Cisco 2950 that someone forgot to replace and it&rsquo;s just been left in the network. If the STP configuration went unmodified, then this switch would likely become the root bridge of our network. Let&rsquo;s look at what that path might look like:</p>
<p><img alt="image" loading="lazy" src="/content/images/2017/11/3-bad.png"></p>
<p>So in this case, SW4&rsquo;s path to the firewall hasn&rsquo;t changed. However, it&rsquo;s best path to SW5 and NET2 is through SW3 - which means any traffic from NET2 to the firewall has to follow the path of SW5 &gt; SW3 &gt; SW4 &gt; SW2 &gt; SW1. Not only does that add more layer 2 hops that NET2 has to pass through, but it also adds more (unnecessary) load onto SW4. What happened if SW4 was so old that it still had 100M ports? It might get overwhelmed pretty quickly.</p>
<p>Now you might be thinking, &ldquo;How often does this really happen&rdquo;? Well, when I started at my last job they were experiencing a similar issue. The primary building had three floors, each with two Cisco 3548 switches to service users. Each of these switches linked back to a pair of Cisco 4500 core switches. All of the 3548 switches were purchased at the same time (far prior to the 4500s), and it turned out that one of them on the third floor had the lowest MAC address in the network. The entire layer 2 topology was then based on this switch as the central point of the network. This caused the interconnects between the core switches to be put into blocking mode - meaning that if a switch on the second floor needed to connect to the alternate core switch, then it would have to pass traffic through the third floor. A quick change to the spanning-tree priority (during a maintenance period) was all that was needed to put the core switches back in charge.</p>
<p>This doesn&rsquo;t immediately make spanning-tree a bad technology. As with just about anything in IT, it&rsquo;s something you need to understand and tune to fit your needs - otherwise you&rsquo;ll just get less-than-ideal results. At another employer, I actually found out that the previous network administrator had manually disabled all of the redundant paths in the network - because he didn&rsquo;t understand STP, and therefore thought that any redundant paths would cause a loop. Spanning-tree isn&rsquo;t something we need to be afraid of - it just needs a little attention.</p>
<p>So next time you&rsquo;re logged into one of the switches in your network, just run <em>show spanning-tree</em> and double-check that the switch you assume is your root bridge actually is.</p>
<hr>
<p>Well I hope that this was helpful. As I mentioned earlier, I meant this as a fairly basic overview - but I intend on diving a bit deeper in later posts. The most fascinating part of networking to me is all the details on how things like spanning-tree actually work behind the scenes.
Have any spanning-tree stories? Leave a comment below</p>
]]></content:encoded>
    </item>
    <item>
      <title>My 2018 Goal: CCIE R&amp;S</title>
      <link>https://0x2142.com/my-2018-goal-ccie-rs/</link>
      <pubDate>Tue, 10 Oct 2017 08:00:46 +0000</pubDate>
      <guid>https://0x2142.com/my-2018-goal-ccie-rs/</guid>
      <description>I&amp;rsquo;m finally starting to work toward one of my long-standing goals: The Cisco CCIE Certification</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>I first completed my CCNA certification back in August of 2007. After that I started working on certifications pretty heavily, because I wanted to learn as much as I could about networking. I used the certifications as both motivation to learn and a measurable goal of my knowledge. Over the next few years I obtained a number of Cisco&rsquo;s associate-level certifications, and by April of 2011 I had finally obtained the CCNP.</p>
<p>Later in 2011 I had changed jobs to a company where certifications were not valued as much. Instead, they urged me to return to school and obtain a college degree. This obviously took up enough of my free time that I really couldn&rsquo;t spend as much time on studying certifications as I wanted to. In 2014 when I needed to re-certify my CCNP, I was just barely able to squeeze together enough time to study for the CCDP ARCH exam. This allowed me to re-certify what I already had, plus gain an additional certification.</p>
<p>Fast forward to early 2017 - I needed to re-certify again. I spent a bit of time trying to figure out what new tests I could study for. If I was going to re-certify then I would rather spend that time learning something new than just re-take a test for something I&rsquo;ve already done. Unfortunately, I was nearing the end of my college degree program, and I just couldn&rsquo;t find the time to dedicate to a new certification - so I ended up re-taking the CCNP TSHOOT exam to re-certify.</p>
<p>After I finished the degree program, I opted to finally take a break for a bit. Even just two months later, and I was <a href="/alright-now-what/">already considering</a> what to do next in terms of certification studies. I wanted to look at Juniper&rsquo;s certification line, since I&rsquo;m more heavily involved in their equipment now - but I also wanted to look at what&rsquo;s next in terms of Cisco certifications.</p>
<p>Well, I&rsquo;ve finally made up my mind, and purchased my first set of books to begin studying for the CCIE R&amp;S. I&rsquo;ve been itching for the past few months to start working on something, but I wasn&rsquo;t really having much luck  making a final decision. However, I was talking recently with our new manager at work about the potential of going to Cisco Live in 2018. This is something I&rsquo;ve inquired about multiple times before and had no luck in getting approval to go. Since we have a new manager, the answer has changed to a &ldquo;Sure, why not?&rdquo;. Since I found out that Cisco Live offers free certification testing (and the CCIE tests are quite expensive), I decided to use that as my motivation to begin studying.</p>
<p>So here goes nothing! My current goal date is June 10th of 2018. By that date I want to be 100% confident in my ability to take and pass the CCIE R&amp;S written exam. I half had the notion of trying to shoot for being prepared for the lab by then, but eight months might be a little too tight of a timeline - at least given what I&rsquo;ve read from other people&rsquo;s experiences. So I&rsquo;ll shoot for the written test by then, with the intent of scheduling the lab soon after.</p>
<p>My current plan is to read through the <a href="https://www.amazon.com/gp/product/1587144921/ref=as_li_tl?ie=UTF8&amp;camp=1789&amp;creative=9325&amp;creativeASIN=1587144921&amp;linkCode=as2&amp;tag=0x2142-20&amp;linkId=bf02add5a14b449046bd01dd3cb8d3ba">CCIE R&amp;S Official Cert Library</a> first, then use that as a gauge to see what I know I&rsquo;ll need a refresh on some of the content from that. I also know that IS-IS is included in the CCIE, which is something that was removed from the CCNP right before I started studying for it. However, it was actually still part of the CCNA content when I took that - so I have a very basic level of understanding. Outside of that - most of my current and recent jobs have focused heavily on switching technologies and less on routing. I&rsquo;ve been working quite substantially with BGP, but not much with internal routing protocols - so that&rsquo;s another point where I&rsquo;ll likely spend additional time.</p>
<p>I know I definitely have a lot to learn, and it&rsquo;s going to be a long several months of study. Obtaining the CCIE certification has been one of my goals since nearly the beginning of my networking career. I&rsquo;m really excited by actually getting the chance to work towards that goal. I&rsquo;m sure I&rsquo;ll be writing a bit here and there as I go through my studies, so look forward to that!</p>
<p>If you have any insight you wish to share, please leave a comment below.</p>
<p>Wish me luck!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Ten years of Cisco Certification</title>
      <link>https://0x2142.com/ten-years-of-cisco-certification/</link>
      <pubDate>Tue, 29 Aug 2017 08:00:41 +0000</pubDate>
      <guid>https://0x2142.com/ten-years-of-cisco-certification/</guid>
      <description>It&amp;rsquo;s hard to belive I started my networking career ten years ago.. How quickly time flies!</description>
      <content:encoded><![CDATA[<p>It&rsquo;s August of 2017 - which means it&rsquo;s been a long ten years since I originally obtained by CCNA certification in 2007. I figured it might be a good time to take a minute and look at what that has meant for me, and how the last ten years of my career have gone.</p>
<p>When I got my CCNA certification, I was only two months out of high school. I had just finished two years of the Cisco Networking Academy coursework, and I had no idea what that would actually mean for me. I went and took the certification exam mostly because I felt that like that was the only way to validate what I had learned during the two year class. I failed it once or twice, which nearly discouraged me enough to not try again. However, I ended up passing the test and becoming Cisco certified on August 27th, 2007.</p>
<p>Obtaining that certification didn&rsquo;t immediately make me valuable to anyone. However, it definitely helped to get my resume in front of a number of people who probably wouldn&rsquo;t have taken a look otherwise. At the time, I had no college degree and absolutely no real-world networking experience. I owe that CCNA cert for helping me get my first job - but after that it was up to me to prove my worth.</p>
<p>It&rsquo;s amazing to sit back and realize that ten years has passed already. So much has happened, so much has changed. I spent the first three or four years of my career studying hard to additional Cisco certs, which I used as motivation to learn more about networking. Certifications can be great for validating what you know, but it&rsquo;s the real-world skill that really pays off in the end. Even with my original intent to become a network admin, I&rsquo;ve ended up wearing a lot of hats and picking up more of a variety of skills than I ever thought I would. It&rsquo;s definitely been a good thing though, since it has allowed me to get a better understanding of other systems - which in turn helps me to better support them as a network admin.</p>
<p>Even though today I don&rsquo;t really manage much in the way of Cisco equipment, the original skills that I learned in the Cisco Networking Academy program have given me a great base knowledge to work with. All of the fundamental networking skills I learned have translated quite well to other vendors and products. I&rsquo;ve spent the past few years working with Brocade, Juniper, Check Point, and a number of other vendors - and I feel like I have had a much easier time picking up the new skills than I might have had otherwise.</p>
<p>Today I still hold and maintain my Cisco certifications - and I plan to continue doing so for the foreseeable future. Someday I would like to achieve a CCIE/CCDE-level certification, but for now I am happy with what I have and what these certifications have helped me to achieve in my career.</p>
<p>Thanks for reading - here is to hoping for the next ten years to be just as good as the last.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Odd Behavior of Protected Switchports</title>
      <link>https://0x2142.com/odd-behavior-of-protected-switchports/</link>
      <pubDate>Tue, 22 Aug 2017 08:00:29 +0000</pubDate>
      <guid>https://0x2142.com/odd-behavior-of-protected-switchports/</guid>
      <description>Some thoughts on an interesting issue I ran into with older Cisco switches &amp;amp; protected port configuration</description>
      <content:encoded><![CDATA[<p>I ran into an interesting issue recently, which was caused by use of the <code>switchport protected</code> command.
So I use a pair of Cisco 2960-8TC-L switches at home, for both my home network and lab. A few months back I ran a bunch of ethernet cabling within my house, which all terminated in a patch panel in the basement. I was able to migrate/consolidate enough of my ports so that I could dedicate one of the 2960s to the patch panel. I had eight ports on my switch, and eight ethernet drops in my house - one of which ran back to my lab network for internet.</p>
<p>Usually when I configure something like this, I want to try and take security into consideration as much as possible. I have a Synology NAS on my home network, which contains enough of my personal backups that I would want to keep this inaccessible from a typical house-guest. So by default, I made the following configuration standards on the ports connected to my patch panel:</p>
<ul>
<li>Any unconnected ports were added to my guest VLAN (which only has internet access)</li>
<li>Any ports that needed to be in my home VLAN were configured with port security, sticky MAC, and maximum 1 MAC allowed</li>
<li>All ports were configured as <code>switchport protected</code> (except the uplink)</li>
</ul>
<p>The concept of protected switchports should be fairly simple: Any port configured with <code>switchport protected</code> is not permitted to communicate with any other port configured with <code>switchport protected</code>. A protected switchport is only permitted to communicate with a non-protected port (in this case, my uplink/trunk to my other 2960). I added this mostly as a safeguard against a potentially malicious house-guest.</p>
<p>However, once I actually began to use my patch panel ports, I began to experience a very interesting issue. For example, I purchased a home security camera which by default used a wireless connection. The location of the camera unfortunately made the wifi connection a bit more unreliable than I would like for a security camera. So I went ahead and ran a cable to the nearest ethernet drop.</p>
<p>The IP camera uses my Synology NAS as a backend storage for any recordings. It was able to connect and stream video on the wireless connection, but the video was choppy. Once I plugged in the ethernet cable the connection actually got worse than it already was. From my Synology - the camera would become unresponsive for a while, then you could reach it again for a few moments, then back to unresponsive (about 60-70% packet loss). If I disabled the wireless NIC entirely, the camera would be completely unresponsive. However, this whole time I was able to reach the camera with no issues from my laptop which was connected via wireless (my AP uses the same switch as the Synology).</p>
<p>The NAS is connected to the 2960 in my lab, which is connected to the patch-panel-2960 via a single trunk port. From the Synology, I could still see ARP entries for my wired camera - I just couldn&rsquo;t reach it via ping or http. I spent a good hour or two trying a number of things: clearing ARP entries, double checking my trunk port configs, and I also upgraded the firmware on the IP camera. Nothing seemed to work. It made even less sense that anything else connected to the Synology-side switch could hit the camera with no problems.</p>
<p>It&rsquo;s worth noting that no ports on the Synology-side 2960 were configured with <em>switchport protected</em> - only the ports on the patch panel side. So I finally tried removing the <code>switchport protected</code> command off of the IP camera port - and magically it all started working.</p>
<p>The protected switchport config worked exactly as I would have expected for traffic between ports on the same switch - however, it seemed to act against what I would have expected once it crossed a trunk to another switch. It was especially odd that it only seemed to crop up between the Synology and the IP camera.
Oh well, I guess the only way you really learn something is by breaking it, right? I hope this might help someone who finds themselves in a similar situation.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Alright - Now What?</title>
      <link>https://0x2142.com/alright-now-what/</link>
      <pubDate>Thu, 11 May 2017 09:13:08 +0000</pubDate>
      <guid>https://0x2142.com/alright-now-what/</guid>
      <description>I finally finished college, so what&amp;rsquo;s next for my professional goals?</description>
      <content:encoded><![CDATA[<p>So it&rsquo;s now been over two months since I finished college and obtained my magical piece of paper. It has been interesting to finally have some free time to do things that I want to do, and not having to constantly balance my time between school and work.</p>
<p>So now that I&rsquo;ve had a bit to sit back and take a break, I&rsquo;m starting to begin itching toward certification studies again. I really enjoy certifications because they give me a goal to work towards, and I can study the materials at my own pace.</p>
<p>For reference, I currently hold the following active certifications:</p>
<p><strong>Cisco:</strong> CCNA, CCNA Security, CCNA Voice (retired), CCDA, CCNP, CCDP</p>
<p>I&rsquo;ve been looking a bit at the Cisco Cloud and Data Center certification tracks, since I&rsquo;m dealing a lot more with the Nexus switching line and data center technologies overall - but after reviewing the cert syllabus, I&rsquo;m not really feeling very strongly toward those. I&rsquo;m also hesitant because it would mean starting back over at the CCNA-level for the new tracks and working my way back up to the CCNP-level. I&rsquo;ve also previously considered getting my CCNP Security, but I&rsquo;m not actively working in Cisco ASA firewalls much any more.</p>
<p>The only next choice in the Cisco world would be going for the CCIE R&amp;S or the CCDE. I&rsquo;ve been considering for a long time that I would eventually like to get there, but those certifications also require a significant investment of time and money. I definitely think the information and skills I would learn along the way would be worth it, and I&rsquo;m beginning to really consider this an option in the near future. I&rsquo;ve spent a bit of time reviewing the exam topics listed on Cisco&rsquo;s site, and debating which of the two would be a better first choice.</p>
<p>My other option is pursuing the Juniper side of things. Most of the data centers I manage now are shifting toward Cisco for switching and Juniper for firewalls - so it would certainly benefit me to educate myself further on the Juniper equipment. Until this point, I&rsquo;ve been just learning on the job by buying Juniper SRX firewalls and figuring it out as I go. My only real hesitation on this would be maintaining two separate lines of certifications. Both Juniper and Cisco enforce a 3-year expiration on their certifications, so I would need to keep on top of both - which isn&rsquo;t necessarily a bad thing.</p>
<p>So at this point, I really don&rsquo;t have a clear idea of what I want to do. Those are my current thoughts and options, but I&rsquo;m having a hard time settling on what would be the best option for me at this time. I definitely want to start studying for something (and potentially take the exam) before the end of this year though, so I would like to figure it out rather soon.</p>
<p>If you have any suggestions or thoughts on the certifications I&rsquo;ve mentioned, leave me a comment below!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Quick Tips for Better BGP</title>
      <link>https://0x2142.com/quick-tips-for-better-bgp/</link>
      <pubDate>Tue, 02 May 2017 10:26:42 +0000</pubDate>
      <guid>https://0x2142.com/quick-tips-for-better-bgp/</guid>
      <description>A lot of BGP setups I run into are bare-bones, but there are a few quick ways to improve your configurations</description>
      <content:encoded><![CDATA[<p>A while back I wrote some basic information on how to get started implementing <a href="/bgp-getting-started-with-multi-homed-internet">multi-homed internet using BGP</a>. The details and configurations listed in that post are enough to get the connection up and running - but not quite in an ideal state. So today I want to share some quick tips that will help you maintain a better and more secure BGP connection.</p>
<h2 id="securing-your-bgp-peeringknow-who-youre-connecting-to">Securing your BGP peering (Know who you&rsquo;re connecting to)</h2>
<p>BGP is a little different from most other routing protocols, since it uses a single unicast TCP connection between peers to exchange routing updates. Lucky for us, that means that we can easily filter traffic from only known peers. Once you have direct connectivity up between your edge router/firewall and your direct peer, lock down that connection with an ACL. Permit TCP port 179 traffic <strong>ONLY</strong> from your directly connected peer IP - no one else.</p>
<p>While you&rsquo;re at it, let&rsquo;s take it another step further: Request that your ISP set up BGP authentication. Sure, a majority of BGP implementations today still require use of MD5 for auth (which is terrible) - but some authentication is still better than none. This can usually be arranged at the time of turning up peering. Both sides configure the same authentication password and with any luck the peering still establishes.</p>
<p>BGP by nature is unfortunately not the most secure protocol - but a few simple steps like this will help ensure you&rsquo;re only connecting out to authorized peers.</p>
<h2 id="route-filtering-dont-trust-anyone">Route filtering (Don&rsquo;t trust anyone)</h2>
<p>Usually when you&rsquo;re filling out the BGP peering paperwork for your service provider, they will ask you what kinds of routes you want. In most cases, you should be able to request one of the following:</p>
<p><strong>Default only</strong> - Exactly what it sounds like. Your provider will only advertise a route for 0.0.0.0/0. In many cases, this is probably what you&rsquo;re going to want. With this type of advertisement, each upstream provider will just give us the same default route to the internet. From there we can weight which one we want to use, and traffic will automatically fail-over to the secondary connection should the primary fail.</p>
<p><strong>Partial</strong> - If for any reason you want to weight routes to certain destinations differently, then we might request this. In this case, you&rsquo;re probably going to still receive 0.0.0.0/0 plus any specific routes you ask for. A good example of this is if we wanted to specifically manipulate routes for a remote office we have. Maybe we want to weight Internet traffic for one uplink, and VPN traffic to a remote office on the other uplink.</p>
<p><strong>Full</strong> - In 99% of typical business cases, this won&rsquo;t be required. This option means the upstream providers will be dumping the <em>entire</em> Internet routing table on you. While this offers you a ton of control over path manipulation, it also requires significant memory resources on your routers in order to maintain that routing table.</p>
<p>After we figure this out, the next step is to make sure we are filtering the routes we accept from the upstream provider. Wait - didn&rsquo;t we just tell them exactly what routes to send us? Why do we need to filter them? Well you can never be too safe here - and we would rather perform an unnecessary filtering than have an ISP accidentally misconfigure route advertisements. So if you&rsquo;re only expecting a route for 0.0.0.0/0, then filter your inbound route advertisements so you only accept that route.</p>
<p>Same thing goes for outbound route advertisement  - if we own a /24 of public IP space, then we only want that range to be advertised out. Some providers may already filter this on their end, but again it doesn&rsquo;t hurt here to be extra cautious. If we are accepting anything other than a default route from our provider, then we run the risk of leaking those additional routes between the two providers - which would lead to inadvertently becoming a transit AS. Chances are pretty good that you don&rsquo;t want that, so make sure you configure filtering for all outbound route advertisements.</p>
<h2 id="minimumadvertisement-oh-no-we-have-to-re-address-everything">Minimum Advertisement (Oh no, we have to re-address everything)</h2>
<p>I mentioned this in the original post - but typically when you are peering with two separate upstream providers, you need to advertise no less than a /24. We ran into this at my last job, where we had been provided a /25 by AT&amp;T but we needed to bring in a second carrier via BGP. The reasoning behind this is to keep global routing tables as small as possible, by not allowing them to end up flooded with a ton of routes for smaller subnets. It makes sense, but on the other hand I feel like requiring a /24 in all cases can be a bit wasteful. My last job only required maybe 30 publicly addressable hosts - which meant that the remaining addresses went unused.</p>
<p>At any rate - should you find yourself in this scenario then you&rsquo;re going to have to face the inevitable: Renumbering into a new IP space. Any time you have to do this, it&rsquo;s going to be a bit of a pain - but for external addressing like this it might be easier. So in our case, the entire /25 space was hosted on our external firewall then NAT&rsquo;ed into DMZ servers.</p>
<p>Here is the quick steps that I used to do a side-by-side migration without taking any significant downtime:</p>
<ul>
<li>Get the new subnet up and running - assign the interface addresses on your firewall and BGP up and running</li>
<li>Assign new IP addresses to all of your existing services</li>
<li>Configure NAT rules for the new external IP addresses to the DMZ hosts - while leaving the existing NAT rules for the old subnet (Also make sure your firewall rules permit the same traffic to either IP)</li>
<li>Migrate DNS entries externally to point to the new IP space</li>
<li>Once traffic stops flowing to the old IP, remove the old NAT</li>
</ul>
<p>As a side note - if you procure redundant internet connections through the <em>same</em> upstream provider, then you might be able to work out something else. They may be able to provide you a private ASN to use, and they will likely accept any minimum advertisement - since they will be summarizing upstream within their network anyways.</p>
<hr>
<p>I had a few more things I originally intended to cover here - but it seems that these topics are filling way more space than I thought they would. Specifically, I&rsquo;m thinking about a dedicated post to BGP path manipulation - which is probably something you&rsquo;re going to want to implement after peering is established.
Hopefully these tips help! If you have any questions, throw them in the comments below.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Guilty Until Proven Innocent: How to Prove It&#39;s Not the Network</title>
      <link>https://0x2142.com/guilty-until-proven-innocent-how-to-prove-its-not-the-network/</link>
      <pubDate>Tue, 11 Apr 2017 08:00:32 +0000</pubDate>
      <guid>https://0x2142.com/guilty-until-proven-innocent-how-to-prove-its-not-the-network/</guid>
      <description>IT&amp;rsquo;S THE NETWORK! - Sound familiar? Let&amp;rsquo;s take a quick look at how we can counter that blame</description>
      <content:encoded><![CDATA[<p>If there is one thing you learn very quickly in networking, it&rsquo;s that everything is always the fault of the network. Two systems cannot communicate? Yeah, that&rsquo;s always a network problem. Something is inaccessible? Probably the network. What about that broken toaster? Definitely a network issue.</p>
<p>Starting off as a less experienced network engineer, this can easily get overwhelming. A ton of other teams blaming the network infrastructure for problems that might be entirely unrelated. However, it seems like a good part of your job will likely be dedicated to proving that the root cause of the problem isn&rsquo;t the network.</p>
<p>I threw together a few tips  that should help you get a better start to defending yourself against the angry sysadmins out there:</p>
<p><strong>Get a good handle on the problem</strong> - Above all else, it&rsquo;s extremely difficult to troubleshoot something without a good description of the problem - so get that first. Which systems are having the problem? Get host names or IP addresses. When did the problem start? Did it ever work? What behavior is being seen? Can it easily be replicated, so that you can watch logs in real-time? If not, get the last date/time that the issue occurred.</p>
<p><strong>Know your infrastructure</strong> - Being a good network admin means understanding traffic flows and routing through your infrastructure. When someone says two systems are having a problem communicating, you should already have a good idea of what network components reside in the space between them. Are these on the same network segment? Is there a firewall (or multiple) in between? Does this traffic utilize a proxy or load balancer?</p>
<p><strong>Check logs</strong> - Once you know which systems are in the way, check through logs for those systems. Particularly with firewalls, do your best to filter out logs to see the traffic calls between each system. Seeing ports blocked or traffic being dropped? A lot of firewall platforms will include enough detail in the traffic logs to quickly identify the issue, if it is in fact a network problem.</p>
<p><strong>The basics are still important: Check TCP flags</strong> - This one has honestly saved me more often than not. Two systems aren&rsquo;t establishing a connection, and the sysadmin says they are just receiving a &ldquo;connection timeout&rdquo; error. Check through the firewall logs - Yeah, we see the typical TCP handshake - but then the remote system sends back a TCP RST to the client. In most cases, this means the connection is actually succeeding from a network perspective. However, the target system is getting something that it doesn&rsquo;t like from the client, so the application kills the session. Same thing goes for a client system sending the RST.</p>
<p><strong>Wireshark</strong> - A lot of people see this as the nuclear option. All else has failed, so we have to resort to a packet capture. I used to think this way too until about a year or two ago. The vast amount of information within a full packet capture can easily be overwhelming - but once you get a handle on how to read it, it can also be incredibly useful. Raw packet captures don&rsquo;t lie - and all the information you need is within those details. Start a capture, reproduce the issue, then analyze the results.</p>
<p><strong>Be patient, and explain your defense</strong> - Not everyone is a network admin, and a lot of IT professionals don&rsquo;t necessarily have a good grasp of how networking truly works. So once you&rsquo;ve gathered your defense, be ready to explain it in a way that the other party will understand clearly. There is a huge difference from saying &ldquo;I see TCP RST packets&rdquo; to trying &ldquo;Looks like the connection is succeeding, but the server-side system is resetting the connection&rdquo;. Some people won&rsquo;t want to admit that the problem actually exists with their system either, so be patient and work with them while they figure it out.</p>
<p><strong>Bonus: Know the application</strong> - In some of my previous jobs, I was responsible for all systems and applications in the environment - even through I was primarily focused on networking. This experience has helped a ton, because even today I can still speak to how some applications work. I have installed and configured VMware ESX, Windows Server, backup and replication products, and much more. So when an application administrator says they are seeing a particular issue with something, I am more easily able to troubleshoot since I have a basic understanding of the applications they&rsquo;re working with and how those applications communicate. This certainly isn&rsquo;t a required skill - but it does help speed up troubleshooting efforts and minimize confusion around what&rsquo;s going on with the application.</p>
<p>Have any other tips you would like to share? Throw them in the comments below!</p>
]]></content:encoded>
    </item>
    <item>
      <title>College vs Certification - Which is better?</title>
      <link>https://0x2142.com/college-vs-certification-which-is-better/</link>
      <pubDate>Tue, 28 Mar 2017 08:00:58 +0000</pubDate>
      <guid>https://0x2142.com/college-vs-certification-which-is-better/</guid>
      <description>My career path is the reverse of most people I&amp;rsquo;ve met - certifications first, then college much later. What impact has this had on my experiences?</description>
      <content:encoded><![CDATA[<p>As of the beginning of this month, I have officially completed my four years of trying to balance working full time and going back to school. I finished up my last college classes and now I can sit back and appreciate having some free time to myself again. I&rsquo;ve never been really into the concept of school, but ultimately I went back because I was being pushed to by my previous employer. So I figured that now is just as good a time as any to tackle the topic of which is better - certs or college degrees?</p>
<p>I talked about this briefly in my initial <a href="/first-a-bit-of-background/">background</a> <a href="/background-story-continued/">story</a> posts, but I went straight from Cisco Networking Academy in high school out to working a full time job at a local IT consulting company. By the time I finished high school, I had already passed the Cisco Certified Network Associate (CCNA) exams and become certified. Having that certification is what got me in the door for a number of interviews, and eventually got me the job at the consulting company. At that point, I really didn&rsquo;t have much else going for me - I didn&rsquo;t have a college education nor any real-world experience. In my time working at this company, I spent a significant amount of time doing self-study and labs for my certification goals. When I got my CCNP certification, I used it along with the experience I had gathered to get my next job. This new employer was heavily focused on their IT staff needing to have a college education - so they pressured me for a while to go back until I eventually gave in.</p>
<p>I spent a while reviewing many colleges in the area and online, trying to figure out what would meet my needs. I ended up picking out a four-year degree in network security, and opted to go the online-only route because it benefited my schedule better. I packed my classes up to a full-time schedule, because I didn&rsquo;t want a four-year degree to take any longer than four years. At this point, I also had the benefit that my employer was willing to reimburse 100% of the costs - which certainly helped convince me to go back.</p>
<p>Over the course of the past four years, I have taken many classes that include general IT, development, networking, and security (not including the normal required materials). I found that a significant portion of these classes didn&rsquo;t directly benefit me. A lot of the material was much more focused toward beginners who haven&rsquo;t already been working in the field for six years - which is completely understandable. The most I really got out of this was improving my abilities to push myself through work that I didn&rsquo;t want to do. I did have a few interesting classes, like an Android development course, which I found to be extremely fun even if I probably won&rsquo;t use the knowledge much.</p>
<p>Four years later and I&rsquo;m done - did I benefit from it? On some level yes, I think I did. At the time of my degree completion, I have now been Cisco certified for ten years and I&rsquo;ve been working in networking nearly the same amount of time. I&rsquo;m already further in my career than I thought I would be at this point, and I&rsquo;m happy with my position and pay (the degree isn&rsquo;t going to change either of these things). At this point in time, finishing the degree is not much more than an accomplishment that I can add to my resume. Sure, having the degree on my resume may get me past HR screening for new jobs and opportunities - but it likely won&rsquo;t actually play much into a company&rsquo;s decision to hire me.</p>
<p>In the end I think that both certifications and college education are useful - they can both be great indications to an employer that you&rsquo;ve been trained on certain technologies or fields. However, I think that the actual on-the-job experience is what really matters - and I experienced a direct benefit from getting in the field early and working while all of my friends were still in college. I would not be as far in my career as I am today if I had waited four more years to start working.  Unfortunately, I think that we place a little too much importance on completing a formalized degree program, when equivalent experience and certifications may benefit a company more.</p>
<p>I understand that I had a bit of a unique situation, but I figured it would be worth sharing my experiences and how they have affected my view of college education. I&rsquo;m still happy that I went through with it and completed the degree, but you won&rsquo;t see me throwing a big celebration - except that I&rsquo;m just super glad it&rsquo;s all finished. At this point, I will take a few months to relax and spend time on hobbies - but I do plan on going back to certification studies (Juniper stuff and likely begin working on a CCIE).</p>
<p>Any thoughts? Comment below with your experiences - I&rsquo;m interested to see if there are many people who have had similar experiences to me, or possibly even the complete opposite.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Why Have a Home Lab?</title>
      <link>https://0x2142.com/why-have-a-home-lab/</link>
      <pubDate>Tue, 21 Mar 2017 08:00:20 +0000</pubDate>
      <guid>https://0x2142.com/why-have-a-home-lab/</guid>
      <description>Ever wonder if running a lab at home is worth it? This post explores why I think it&amp;rsquo;s an important investment</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>If you really want to become great at something, you practice it a ton, right? Well networking and IT work exactly the same. You&rsquo;re not going to become an expect by just reading a ton of tech books and blogs. While those certainly help, there is nothing better than simply getting your hands dirty. Having a good home lab setup is key to truly understanding how things work.</p>
<p>So how do you get started? Well the way that I built a home lab over the past 10 years is probably much different from you could today, given the amount of virtualization technologies available. Still, I believe that some physical pieces of equipment are necessary. I took classes in high school toward CCNA certification, and we had a lab of several routers and switches there. Once I got into the real world, I wanted to start working on additional certifications and just improve my skills overall. So I picked up an old Cisco 2611 router and a 2950 switch. I played with these for a bit and used them to get my CCNA Security, which at the time covered the basics of securing Cisco IOS routers and switches.</p>
<p>Another year or so down the road and I expanded by picking up a power-over-ethernet switch, and two Cisco 7900 series IP phones. Since I had discovered that the 2611 router could run Cisco&rsquo;s Call Manager Express, I decided to go for the CCNA Voice certification. Having this equipment to work on gave me experience that was much closer to real world, than if I had just studied the textbooks. I could configure things, break things, then sit there for hours until I figured out how to fix my problem. I could configure the entire system, test it all, then tear it down and completely rebuild. Being able to configure the entire CME system from memory gave me a lot of confidence toward taking the certification exam.</p>
<p>So do I still have a home lab today? Oh yeah, you bet I do! It&rsquo;s changed quite a bit from what it used to be, but the same concept still applies. I have an entire environment to play with, which allows me to test and learn new technologies outside of work. In fact, my &lsquo;home lab&rsquo; has evolved into just part of my home networks.
So here is what I&rsquo;ve got running today:</p>
<ul>
<li>Cisco ASA 5505 (Probably soon to be replaced with a <a href="https://www.amazon.com/gp/product/B01ICEO2U4/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;tag=0x2142-20&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B01ICEO2U4&amp;linkId=35fbe8300af4e5d1e26e7a860782b3ca">Juniper SRX 300</a>)</li>
<li>Two Cisco 2960G-8TC-L switches</li>
<li><a href="https://www.amazon.com/gp/product/B015PR20GY/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;tag=0x2142-20&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B015PR20GY&amp;linkId=864af1f40df3f986b49741655d21e926">Ubiquiti UniFi</a> 802.11n wireless access point</li>
<li>Synology DS411 Network Attached Storage with 4x 3TB drives (Soon to be replaced, as it is over 5 years old(Update: Got myself a <a href="https://www.amazon.com/gp/product/B075N1Z9LT/ref=as_li_qf_asin_il_tl?ie=UTF8&amp;tag=0x2142-20&amp;creative=9325&amp;linkCode=as2&amp;creativeASIN=B075N1Z9LT&amp;linkId=c2891aca5bc28b1ebf25847b6e687135">DS918+</a>!))</li>
<li>A few spare PCs running VMware ESX 6.0</li>
</ul>
<p>The ASA, switches, and AP run just about all of my home network. I even have the ASA running AnyConnect SSL VPN so I can access my storage at home from anywhere. The Synology has been one of the best additions to my network and lab. For one, it acts as a centralized storage device for my home network. I back up all of my PCs to it, and any digital media I own is also stored on it so I can stream it to devices within my home. For two, the Synology acts as an iSCSI backend to my VMware hosts. This setup allows me much more flexibility with my lab.</p>
<p>On the ESX hosts, I have a few VMs for lab use and a few that are for my home network. A GitLab server hosts all of my Git repositories for my own personal coding projects. I have a CentOS box for running the Ubiquiti management web interface. Another few CentOS VMs for running bind DNS, Observium, and Splunk. I also run a personal Minecraft server on there, so it&rsquo;s not all work here 🙂</p>
<p>I love the idea that at any time I can just go home, spin up a few VMs, and start playing with something new. When I was learning Juniper&rsquo;s SRX platform, I downloaded their free trial of the vSRX and had it running for a while. When I changed jobs, I needed to learn a new web proxy software - so I downloaded their free trial and stood up a VM. You really learn a lot by building a platform from scratch, because you gain a better understanding of what impact certain configuration options have. You also have the freedom to change whatever settings you want and see what they do. I once had an idea for a coding project, so I turned up a VM running RabbitMQ - and spent a weekend learning how it works to see if it would accomplish what I needed for the project.</p>
<p>So to sum it up - I just want to say that having a home lab has really contributed a lot to my success. It offers way more flexibility than trying to test something at work, unless they also offer you a complete lab environment. Your lab doesn&rsquo;t have to start off perfect, nor does it need to have expensive equipment - it just needs to help facilitate your ability to learn and gain experience.
Have a lab at home? Tell me about it in the comments below! I would love to hear what other people have done.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Port Security: Worth the effort?</title>
      <link>https://0x2142.com/port-security-worth-the-effort/</link>
      <pubDate>Tue, 14 Mar 2017 08:00:40 +0000</pubDate>
      <guid>https://0x2142.com/port-security-worth-the-effort/</guid>
      <description>An exploration of practical applications of port-security configurations, based on my past experiences</description>
      <content:encoded><![CDATA[<p>Port Security. Always seems like one of those things covered in Cisco exams, yet how many businesses actually use it? For those that aren&rsquo;t implementing it, should they? Or is it too much of a headache?</p>
<p>So the concept of port security is fairly simple - We want to secure each individual switch port to a physical layer 2 MAC address, or at least limit how many unique MAC addresses might be learned on an individual port. The technology could be used to just limit the number of simultaneous devices on a port - by just setting a MAC threshold. Or we can also take it to the extreme and lock down each port to a hard-coded MAC address - which will never allow another device to connect. You might be thinking that the second method is absolutely ridiculous, but it really depends on the business needs.</p>
<p>First, let&rsquo;s take brief look at the typical port security configuration and some of the options available.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">SecureSwitch(config)# interface x/x  ! Whichever interface we want to lock down
</span></span><span class="line"><span class="cl">SecureSwitch(config-if)# switchport port-security max xx  ! Max number of MAC addresses that can be learned
</span></span><span class="line"><span class="cl">SecureSwitch(config-if)# switchport port-security violation xxxxx  ! Choose to either restrict or shutdown the port (description below)
</span></span><span class="line"><span class="cl">SecureSwitch(config-if)# switchport port-security  ! This actually enables the port security config
</span></span></code></pre></div><p>Fairly straight-forward, right? We choose a port (or you could do a range) and set a few options. The default number of MAC addresses able to be learned on a port is 1, so it&rsquo;s likely you&rsquo;re going to want to change this - unless 1 is all you need. Port security can only be enabled on <strong>access ports</strong>, so 1 MAC address works in most cases  - except where you have a PC daisy-chained off of an IP phone (in which case this will need to be set to 2 or 3).</p>
<p>Next we set our preferred violation action. This step is pretty important because it defines what happens when the port exceeds it&rsquo;s MAC count. <strong>Restrict</strong> is the passive approach. If we have two PC&rsquo;s plugged into a single access port (maybe using an unmanaged switch), then the second PC will just never be able to work as long as our max MAC limit is 1. The first PC to connect will be fine, and the switch will log a message and send an SNMP trap when the second MAC is picked up. <strong>Shutdown</strong> is the more forceful approach. Once that second MAC address turns up on the port, the switch puts the port in an err-disabled state - which shuts down the port to all traffic. This event is also logged and generates an SNMP trap - however the port will not come back online until an administrator manually re-enables it.</p>
<p>Now that we see a basic config, let&rsquo;s take a look at a few different use cases for this feature. In one of my previous jobs, I worked as a network admin for a local government organization. Port security configuration in that environment was <em>extremely</em> strict. Each switchport was configured to permit only one MAC address, shutdown upon violation, and the <code>switchport port-security mac-address sticky</code> command was also used. This command takes the first MAC address learned on the port and commits it to the running configuration, which means that this MAC is essentially hard-coded to be the <strong>only</strong> MAC permitted on the port. So in this environment, a single PC was tied to a single port - nothing else could ever be plugged into that port without either shutting down the port or administrator intervention. In a government office, this was absolutely necessary because every device on the network needed to be tracked and personal devices were not permitted to be connected. We needed to know if anything was ever plugged in that wasn&rsquo;t an authorized device - so manual intervention and investigation was a requirement.</p>
<p>In a more typical office environment - port security configurations can just be a good security practice without going overboard with it. We never want a user to plug in a rouge switch into our network without our knowledge, right? So maybe we assume each user has an IP phone and PC, and limit the port to 2 MAC addresses. In this case, we can go ahead and just set the port to restrict. We don&rsquo;t want to prevent the user from working if a port violation occurs, nor do we want to spend time resetting the port for them - but we might still want to be notified, especially if it happens often. In addition, port security is an excellent way to secure ports public areas. For example, maybe we have an IP phone or kiosk PC in our lobby. These need access to the network, but we don&rsquo;t want anyone to be able to unplug that device and gain access into our network. In cases like this, it would actually make sense to have the switch only permit access from that single MAC address.</p>
<p>Outside of the &lsquo;practical&rsquo; use cases, there is also the strictly security side of things. I&rsquo;ve touched on a few considerations already - but there are also certain types of attacks that can be defeated by port security. One of those would be exhausting the CAM table resources. A malicious person could use publicly available tools to spoof MAC addresses in the packets they send to the switch. Tools like this force the switch to learn hundreds of thousands of MAC addresses, which eventually will overload the CAM table. When a switch CAM table becomes full, the switch begins flooding packets out all interfaces. This is because the switch can no longer assign mappings between MAC addresses and the ports they originate from - so the switch has no choice but to flood everything and hope the correct recipient receives the data. For the attacker, this means they can run a packet capture on the port and collect information they wouldn&rsquo;t have otherwise needed to. This scenario could be prevented by implementing port security, which could simply restrict the number of MAC addresses learned off of any individual interface.</p>
<p>Port security configuration can be implemented in a few different ways depending on your use case. Overall though, it can prove to be a useful way to help implement security controls on your network.  What do you think about port security? Extremely useful or does it just get in the way? Comment below and tell me how you have implemented it!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Virtual Networking Contexts</title>
      <link>https://0x2142.com/virtual-networking-contexts/</link>
      <pubDate>Tue, 21 Feb 2017 08:00:58 +0000</pubDate>
      <guid>https://0x2142.com/virtual-networking-contexts/</guid>
      <description>A brief exploration into why VRFs can be extremely helpful</description>
      <content:encoded><![CDATA[<p>I really want to take a moment to talk about how wonderful VRFs/firewall contexts really are. Both technologies essentially allow a network administrator to spin up a virtualized, isolated instance of a network device. I&rsquo;ll be honest and say that I hadn&rsquo;t had the chance to play much with this stuff until just recently - but it makes life a lot easier in a cloud provider environment.</p>
<p>I&rsquo;ve been looking for a good chance to use VRFs in the past, but in most cases it didn&rsquo;t really make much sense. About a year ago, I had a great opportunity when we needed to build a new data center. The data center was aimed at being lower capacity than most of our other locations, so we had to cut some costs here and there. In all of our other locations we use two physically separate sets of firewalls, one for external traffic and one for internal traffic. In this new location, we opted to save some money by picking up only a single pair of Juniper SRX 345 firewalls.</p>
<p>I made the decision here to make use of Juniper&rsquo;s virtual routing instances to keep logical separation of internal vs external firewalling, even though it was only a single physical cluster. For one, this would allow existing staff to maintain their current understanding of network architecture. Every data center has the same overall logical traffic flow, even if the physical devices are different. Second, this allowed us to split load across the two devices. Normally we have two physical clusters to handle the traffic load, but in this case we were essentially going to pump the same traffic through one pair of firewalls. Assigning each virtual routing instance into its own redundancy group allowed us to run each firewall instance on a separate device - yet still allow for both instances to run on one in the event of a failure.</p>
<p>Once we got that firewall cluster into production, there seemed to be a lot less fear regarding virtualized network contexts. I was able to prove that it worked, and worked well for what we needed at the time. Soon enough I was able to find a few additional places where we could make use of the same concepts. We recently procured quite a few Cisco Nexus 9372PX switches for both new deployments and hardware refreshes. By default these switches already come pre-configured with a out-of-band management VRF, which is already super useful to me. We run all of our device management traffic on a segregated network, so a management VRF allowed me to configure the IP/route information to make all that work - while not interfering with the normal layer 3 operations of the device.</p>
<p>Being a cloud provider, most of our customers are completely abstracted from the hardware/software that runs their hosted applications. However, in a few cases there are instances where a customer negotiates for a contract change to say otherwise. For example, a customer might have a special software integration they want to run and have the ability to control - or some customers want a dedicated point-to-point Ethernet connection into one of our data centers for increased reliability. A lot of the background networking work for this in the past was a bit of a pain - but it opened up another opportunity to make use of VRFs. I now have a dedicated customer VRF, which has separate routing configurations than our normal production environment. Customer wants to stand up BGP peers across their direct connection to our data center? Sure, I can isolate that BGP instance in the customer VRF, so there is no conflict with our production routing tables.</p>
<p>I&rsquo;m sure that my current use cases are probably not the ideal implementations of virtual networking contexts - but they work for what we need and they make life a lot easier. I can see these becoming more and more common in our environment to logically segregate traffic. I am interested to hear how other companies have integrated this type of technology into their networks - so leave a comment below!</p>
]]></content:encoded>
    </item>
    <item>
      <title>A Little Bit of Magic</title>
      <link>https://0x2142.com/a-little-bit-of-magic/</link>
      <pubDate>Tue, 07 Feb 2017 08:00:03 +0000</pubDate>
      <guid>https://0x2142.com/a-little-bit-of-magic/</guid>
      <description>A good network engineer can almost feel like a magician - fixing things behind the scenes &amp;amp; detecting problems early.</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve lost track of the amount of times in my career that someone has said &ldquo;How did you do that?&rdquo;, &ldquo;Wow that&rsquo;s amazing!&rdquo; or &ldquo;I would have never figured that out&rdquo;. My answer is typically that it involved a little bit of magic - but then I follow it up with an actual explanation. For those less technical, some things really can seem like a bit of magic. Solved an outage in minutes? Knew about an impending issue before it happened? Yeah - that can be quite magical.</p>
<p>So I have a few posts that I plan on scattering here and there, which will cover some tips on how to become a networking magician. I will aim to provide detail behind some of the expert intuition and skills which can amaze and confuse others. Let&rsquo;s get started with Magic Tip #1:</p>
<h2 id="monitor-your-network">Monitor your network</h2>
<p>No, really, just monitor your network. I don&rsquo;t mean &ldquo;Oh, there is a ping alert for that switch&rdquo; - I mean pay attention. How will you ever know what an anomaly looks like, if you don&rsquo;t have an internal baseline of what your network should look like? This tip really takes time, but I&rsquo;ve found that it pays off in the long run.</p>
<p>I use a couple of open-source tools and applications, like <a href="http://www.observium.org">Observium</a> and <a href="http://oss.oetiker.ch/smokeping">SmokePing</a>, to track metrics on my networks. I spend a quick 5-10 minutes each morning quickly skimming through the pretty graphs to get an idea of how we are performing today. About once every or every other week, I will spend a bit more time for a deeper dive into the metrics. However, the important thing here is not the time spent, but the fact that I look at these. In the back of my mind, I keep a mental note of the general averages for bandwidth, latency, packet loss, etc.</p>
<p>Once in a while, I might look at a graph and notice that something is a bit off. Defining the word &lsquo;off&rsquo; in this sense is difficult. Maybe a router interface that averages 20Mb/s spiked to over 40Mb/s through the night. Maybe traffic was actually far lower than the average. Sometimes I might see a slight increase or drop in latency between a pair of data centers. Some of these things could mean absolutely nothing - but in many cases they are an indicator of something else.</p>
<p>As an example to this - A few weeks ago, I noticed that the average latency between two data centers had increased slightly, and SmokePing was reporting occasional packet loss of up to 5%. I also track historical traceroute tests - so when I reviewed those, I found that the upstream carrier&rsquo;s route had changed about 2-3 hops out. No big issues - but I made a note of these findings. A few days later, we began experiencing a spike in packet loss between those two data centers. Rather than being caught completely off-guard, I already had all of the information I needed to work with the upstream carrier. Issue resolved - quickly, simply, and without wasting time during a network degradation event.</p>
<p>Let me just reiterate that I don&rsquo;t expect everyone out there to stare at bandwidth graphs all day long - that&rsquo;s not going to get you anywhere. However, we do need to spend a little bit of time giving our network the attention that it deserves, even if its just a quick check-up every day. Once you have a good idea of how things typically operate, it can be much simpler to pinpoint issues and get ahead of them - which means being resolved without wasting time.</p>
<p>Ever had someone claim you&rsquo;ve performed magic? Tell me about your experiences in the comments!</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Small Things (0x2142)</title>
      <link>https://0x2142.com/the-small-things-0x2142/</link>
      <pubDate>Tue, 17 Jan 2017 08:00:34 +0000</pubDate>
      <guid>https://0x2142.com/the-small-things-0x2142/</guid>
      <description>Sometimes the small details are what matter the most</description>
      <content:encoded><![CDATA[<p>Even when you&rsquo;re ten years or so into your career, you can always stand to learn something. It&rsquo;s important that no matter how experienced you get, you always keep an open mind to other people&rsquo;s ideas or opinions. As an example to this, I would like to share the story of this blog name.</p>
<p>Back when I worked at a local IT consulting company, they hired a network admin who had worked as several large service providers in the past. He was very experienced and intelligent, and was able to walk into the organization and immediately begin making positive changes. Exactly the type of person that you would want to hire, right?</p>
<p>Well after a few months in, he began checking through some of the equipment we had in our spare store-room. A bunch of Cisco routers and switches, some older than others. After a week, he began complaining about how the devices had sat on the shelves too long. It seemed as though the flash memory was degraded, which caused the devices to not retain their configuration settings. Almost every device he checked through seemed to be experiencing this issue. What else can you do at this point but throw out the bad hardware?</p>
<p>So I decided to pick up one of the devices to see what he was talking about. After all, I was still very early in my career - so if I could stand to learn something from how the devices were behaving, I wanted to see it. So I boot up an old Cisco 2610 router and make a few configuration changes. Save, reboot, and sure enough my changes were gone. However, I had also just been studying how to password reset these devices - since I had a pile of them that needed to be reset. Part of resetting the devices was booting into rommon mode and changing the configuration register value to a hex value of 0x2142.</p>
<p>So what is 0x2142? It&rsquo;s a hex value that tells the router upon boot to ignore any saved configuration. Of course that easily explained the &ldquo;degraded flash&rdquo; issue that the experienced network admin had seen. So I changed the configuration register back to 0x2102, made a few more configuration changes, then rebooted. Sure enough, everything was still there. So I went and told the network admin what I had found. &ldquo;Oh, checking up on me, huh?&rdquo;</p>
<p>This story has been a bit of a running joke for a while. But really the importance is that even when you&rsquo;re extremely intelligent and experienced, you can still overlook simple things. He had been password resetting the devices, but never reverting the configuration register values back to the defaults. Even when you think you might know everything, you should still keep an open mind - because even someone with no experience might have a different view on something. Sure, this wasn&rsquo;t really a big &ldquo;save the day&rdquo; moment, but it helped to show that guy that I had some idea of what I was talking about. From then on, he actually began to work with me on understanding more networking concepts and started asking me to help out with some more of the work he was doing.</p>
<p>What was the most ridiculous simple mistake you&rsquo;ve made? And how did you find out about it? Share in the comments!</p>
]]></content:encoded>
    </item>
    <item>
      <title>BGP: Getting Started with Multi-homed Internet</title>
      <link>https://0x2142.com/bgp-getting-started-with-multi-homed-internet/</link>
      <pubDate>Tue, 10 Jan 2017 08:00:17 +0000</pubDate>
      <guid>https://0x2142.com/bgp-getting-started-with-multi-homed-internet/</guid>
      <description>Exploring which design/setup questions to ask, and how to begin a basic configuration</description>
      <content:encoded><![CDATA[<p>A few years back I worked for an organization that had a single 100Mb Internet connection. Not bad for just typical corporate traffic, but we also hosted our production web site out of that location as well. An incident occurred where our website was down due to Internet issues during an extremely inconvenient time. So we decided to procure a second Internet uplink through a different provider. At the time, I had no practical experience doing something like this - yet I was put in charge of the project. Let&rsquo;s go over some of what I learned&hellip;</p>
<p>The easy part of the whole process is the first step - ordering a second Internet connection. Our CIO at the time placed a few calls and had a quote back pretty quickly. A local carrier was willing to run new fiber cables to our building in less than a month. Depending on how important uptime is to your organization, this is the point where you might want to ask about a diverse path into the building. If both connections run though the same physical paths, then a single incident could still cause an outage. For example - I once worked somewhere where the redundant Internet connections shared the same telephone poll across the street. So even though the connections were redundant, a single accident involving that telephone poll and both connections were severed.</p>
<p>Next - Ask about IP space. In terms of IPv4, the general rule for external BGP peering is that ISP&rsquo;s don&rsquo;t like to accept any prefixes smaller than a /24. In our case, we had a single /25 block already allocated by our current provider - which wasn&rsquo;t going to work. Luckily, the new service provider offered to give up a free /24 block along with the installation costs. Unfortunately, this meant that we had to re-address all of our public-facing services, which is almost always a pain to do. I have a few tips for this, which helped us to minimize downtime - but that&rsquo;s a story for another time.</p>
<p>Next, we need to obtain a globally unique Autonomous System (AS) number, which will be used to advertise our network to the world. Since we were located in North America, we went though <a href="https://www.arin.net">ARIN</a> for this process - which was fairly painless. Sign up for an account, prove that you&rsquo;re associated with the business, fill out a few forms to justify your need, and then just wait for the approval. One thing to watch out for is 2-byte vs 4-byte AS numbers. 2-byte is the standard and has been around forever, but only allows for up to 65,535 unique IDs. A 4-byte ASN allows for significantly more unique IDs, but I have actually run into instances where an ISP doesn&rsquo;t support these. I would hope that in most cases a 4-byte ASN will be just fine, but it might be worth asking your ISP just in case.</p>
<p>At this point, you should be ready to hit the ground running as soon as that second Internet uplink is installed. This is also assuming you already run a router or multilayer switch on the edge of your network, which also has BGP capabilities. So let&rsquo;s get down to the fun stuff - an extremely basic configuration to peer between two ISPs. I&rsquo;ll dedicate another post to additional recommended settings and configurations - but for now let&rsquo;s focus on getting this running. The configuration sample below is aimed at Cisco devices, but the same concepts apply to most vendors:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">EdgeRouter(config)# router bgp *&lt;YOUR AS NUMBER&gt;  *! The AS number provided by ARIN
</span></span><span class="line"><span class="cl">EdgeRouter(config-router)# network *&lt;YOUR LOCAL SUBNET&gt;*   ! The subnet we need to advertise out both ISPs
</span></span><span class="line"><span class="cl">EdgeRouter(config-router)# neighbor *&lt;ISP1 PEER IP&gt;* remote-as *&lt;ISP1 ASN&gt;* ! Provided by the first ISP - Their remote peer IP and ASN
</span></span><span class="line"><span class="cl">EdgeRouter(config-router)# neighbor *&lt;ISP2 PEER IP&gt; *remote-as *&lt;ISP2 ASN&gt; *! Provided by the second ISP
</span></span></code></pre></div><p>As I mentioned, this config is very basic and will just accomplish what we need to get going. Follow up with a quick <code>show ip bgp neighbors</code> and hopefully you&rsquo;ll see two peers in the <em>established</em> state. Any other state indicates a problem bringing up the peer connection. I won&rsquo;t get into too much detail here - but check the physical connection, ping the peer, and make sure there are no firewalls blocking TCP port 179 between the peer addresses.</p>
<p>Hope this was helpful! Comment below and let me know how your experiences have gone with this type of setup - and look forward to a few more posts regarding BGP peering setup with multiple ISPs.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Background Story (Continued)</title>
      <link>https://0x2142.com/background-story-continued/</link>
      <pubDate>Tue, 20 Dec 2016 08:00:09 +0000</pubDate>
      <guid>https://0x2142.com/background-story-continued/</guid>
      <description>(Part 2 of 2) A brief summary of my networking &amp;amp; career experience</description>
      <content:encoded><![CDATA[<p>This post is a continuation of last week&rsquo;s &ldquo;<a href="/first-a-bit-of-background/">First, A Bit of Background</a>&rdquo;</p>
<hr>
<p>So once I had that magical CCNP certification, I finally felt like I needed to move on. I had gained as much experience from that first job as I thought I would, which meant that I needed to start looking. I got some help from a co-worker of mine at the time, who gave me some wonderful resume tips (which I will share in a future post). Two months and a handful of interviews later, and I found myself jumping on a contract-to-hire position for a local government organization.</p>
<p>The three and a half years spent with this organization taught me so much. I had a great boss, to whom I owe many personal improvements that helped me get where I am today. I walked into the place in a role that was technically supposed to be a Junior Systems Administrator, but the position was much more widely focused than that. I did everything and anything, including managing an Avaya phone system, desktop support, networking, Windows administration, and even a bit of VMware ESX. Obviously, I began to lean more and more toward the networking side of the house, as the team was relatively well split in terms of specializations. One guy loved virtualization and storage, another loved application support, and I owned all things networking.</p>
<p>Another thing this job brought me was the push I needed to go back to school. The organization didn&rsquo;t like to hire people without a college degree, but I managed to make it in under a very rare set of circumstances. Unfortunately, that meant that I was constantly told that I really need to go back to school and get a degree. After a short while, I gave in and picked up a four-year online degree program in Network Security.</p>
<p>This place was my first real experience in actually <em>owning</em> a network. Having complete control and being able to call it my own. I spent the first couple of months doing exploratory research - what did we have running and how was it configured. Then I built a list of recommendations for things I thought needed to be improved. After a few years, I had replaced almost every device (many were end of life) and made the network significantly more secure and resilient. I had many great learning opportunities in managing my own time and building project plans. I designed network upgrades and made detailed plans to make it all work - and it did, surprisingly.</p>
<p>While that job was an absolutely amazing experience for me in terms of personal and career growth, I eventually reached a point where those things slowed down. Soon the negative aspects of the job were starting to outweigh the positives, and so I began my job search once more. A friend of mine, who I had previously worked with at the consulting company, ended up referring me to a position with a company he worked for. The position was a Network Administrator for a local cloud Software as a Service provider.</p>
<p>I didn&rsquo;t know it when I took the job, but I ended up walking into an environment where I had the most experience on the team. For having several datacenters around the world, the network architecture left much to be desired - A lot of designs built upon the need of the moment and not the future. At the time of this writing, I&rsquo;m still with this company - and I&rsquo;ve already gained quite a different set of skill and experiences: Being the senior team member, designing scalable network architecture, and learning the ability to lead others.</p>
<p>I&rsquo;m going to stop here with my story for now - but hopefully this provides a bit of context around where my experiences and insight have come from. I have a lot of future post ideas which will build upon everything that I have learned over the past ten years.
Thanks for reading!</p>
]]></content:encoded>
    </item>
    <item>
      <title>First, A Bit of Background</title>
      <link>https://0x2142.com/first-a-bit-of-background/</link>
      <pubDate>Tue, 13 Dec 2016 08:00:30 +0000</pubDate>
      <guid>https://0x2142.com/first-a-bit-of-background/</guid>
      <description>(Part 1 of 2) A brief summary of my networking &amp;amp; career experience</description>
      <content:encoded><![CDATA[<p>I wanted to start off my providing a little background on myself. Hopefully this will put some context around my future posts.</p>
<p>In the beginning - I started off doing some minor PC repair for family and friends. Really quite minor stuff, like replacing power supplies, reinstalling the operating system, or troubleshooting application issues. The technical work really was fun for me, but at that point I had never considered the possibility of it becoming a career. It just seemed like a fun hobby that was great to do in my spare time.</p>
<p>After I completed my second year of high school, I found out that I would have to change schools. Luckily, I found out that my new high school offered this fun program called the <a href="https://www.netacad.com">Cisco Networking Academy</a>. The program was three hours a day for two years, and taught all of the networking fundamentals necessary to pass the Cisco Certified Network Associate (CCNA) exam. I quickly found that this is something that I truly enjoy doing and I was actually good at it. We had quite a few networking professionals come into the class over those two years and tell stories of how successful a career in computer networking could be. That was the point where I realized that this might actually be a career option - so I went with it.</p>
<p>Within two months of finishing high school, I took and passed the CCNA exam. Cisco certified at the age of eighteen, and now left wondering how to find a job. My next stroke of luck came in the form of a family member who had actively been working in IT for about 10-15 years already. She sat down with me and helped me build my first resume, then showed me where to post it online. Within a few weeks, I began receiving calls from recruiters in the area about a variety of positions. &ldquo;Level 1 Help desk? No, I want to be a Network Engineer making ALL THE MONIES&rdquo;. Of course at the time, I had no idea that jumping directly into a network engineer position was very unlikely - especially given that I had no real world experience yet.</p>
<p>A couple interviews and a few months later, and I happened upon a local IT consulting company. I remember interviewing with the manager at the time and mentioning how difficult it was to find a job, since everyone wants you to have experience but no one wants to help you get it. Well, he decided that he was willing to help out and offered me a job as a Level 1 Network Operations Center Engineer.</p>
<p>I spent nearly four long years at that job. I was new to the field so I took advantage of every opportunity they offered me. Certification training? Yes. Networking projects? Yes. Consulting for a variety of businesses? Yep! The company culture was heavily focused on making money quickly, which meant that they didn&rsquo;t always take care of the employees very well - but there is something to be said about the amount of varied experience I gained, especially for my first real tech job. While I was working here, I also added onto my collection of Cisco certifications: CCNA Voice, CCNA Security, CCDA. I finally finished up by achieving one of my goals of becoming CCNP certified.</p>
<hr>
<p>So this has been part one of my history, and to make this a bit more readable I&rsquo;m going to split it into two postings. Continue the story in the <a href="/background-story-continued/">next</a> post!</p>
]]></content:encoded>
    </item>
    <item>
      <title>A New Start</title>
      <link>https://0x2142.com/a-new-start/</link>
      <pubDate>Tue, 06 Dec 2016 08:46:07 +0000</pubDate>
      <guid>https://0x2142.com/a-new-start/</guid>
      <description>A quick look at my intentions for this blog</description>
      <content:encoded><![CDATA[<p>Over the years I have made several attempts at starting a blog. A few on networking, general IT, or whatever came to mind. They all end up the same - I start off strong and fall off quick. Finally, I believe I&rsquo;ve realized what my problem is: I always assumed that a successful blog had to be purpose-built and constantly kept up to date with new and exciting content.</p>
<p>So here I am again, giving this another shot. This time I won&rsquo;t be backing myself into a corner from the start. This blog is intended to be networking oriented but with a bit of a wider focus. I&rsquo;ve already come up with quite a few ideas for content I would like to write here, so I&rsquo;m more prepared. That being said - I&rsquo;m not committing to regular updates or always exciting content. When I have something I feel is worth sharing, I will share it.</p>
<p>So to provide a general overview, here is the outline of topic ideas I have for this blog:</p>
<p><strong>Education/Certification Studies</strong>- Every network admin has a blog to document their road to CCIE certification, right? This has certainly been one of my goals over the years, so I&rsquo;ll be writing about the awesome things that I learn. This is also meant to include general networking education topics, since you can never stop learning.</p>
<p><strong>Career</strong> - I&rsquo;ve been Cisco certified and working in networking for nearly ten years, but that doesn&rsquo;t mean everyone has. I&rsquo;ve finally reached the point in my career where I&rsquo;m meeting a lot of people new to the field and I&rsquo;m able to help guide them. So I would like to share some of the career advice that I have, both from my own experiences and advice that I have received from others.</p>
<p><strong>Network Design/Architecture</strong>- This stuff is really important, as I&rsquo;ve run into more than enough situations where a network wasn&rsquo;t originally designed for the type of workload it handles today. I want to cover both network design topics, as well as why it is important. I have some stories to share on how bad network architectures can have significant consequences.</p>
<p><strong>A Little Bit of Magic</strong> - This is probably my favorite topic. You ever work with an IT professional who just somehow knows how to fix everything? The person who can pick up almost any technology and become an overnight expert in it? Well, I would like to share some of my insight into how this type of thing is accomplished, and why it just seems so magical.</p>
<p>This likely won&rsquo;t be everything I cover here, but these are the primary topics. I&rsquo;m going to give this site my best shot over the next few months - so let&rsquo;s see how it goes.</p>
<p>Feel free to bug me in the comments with any questions!</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
