<?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>Meraki on 0x2142 | Networking Nonsense</title>
    <link>https://0x2142.com/tags/meraki/</link>
    <description>Recent content in Meraki 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, 11 Mar 2021 20:21:34 +0000</lastBuildDate>
    <atom:link href="https://0x2142.com/tags/meraki/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>From Postman to Python: Your First GET Request</title>
      <link>https://0x2142.com/from-postman-to-python-your-first-get-request/</link>
      <pubDate>Thu, 11 Mar 2021 20:21:34 +0000</pubDate>
      <guid>https://0x2142.com/from-postman-to-python-your-first-get-request/</guid>
      <description>Let&amp;rsquo;s walk through a few simple Postman requests, and show how to do the same with 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/nOdIyrA2l5A?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>Maybe it&rsquo;s just me - but I feel like when we want to demonstrate product APIs to someone, we usually jump into Postman first. It&rsquo;s not without good reason though. Postman is a very easy to use platform for running API calls against REST endpoints &amp; see the nicely formatted output.</p>
<p>That being said, often times I&rsquo;ve seen that we stop there. We might mention Python as an advanced method of using our APIs, or maybe we talk about how &ldquo;if APIs are the new CLI, Postman is the new PuTTY&rdquo;. I&rsquo;ve seen some engineers who hear these statements and feel like Postman is being pushed on them - even when they&rsquo;re perfectly happy with an SSH-based CLI.</p>
<p>Network automation isn&rsquo;t usually accomplished with just one tool - it&rsquo;s a whole storage closet full of different tools &amp; utilities, each with their own use cases or specializations. In this particular example - learning Python allows you to move beyond one-off API calls in Postman, and into being able to accomplish much more complex automation.</p>
<p>So the purpose of this post is to explore how to get beyond just Postman, and into using Python for REST API calls. This isn&rsquo;t going to be a complete run-down of all the capabilities of both tools - but rather a few examples of simple GET requests using both methods.</p>
<p>If you don&rsquo;t have Postman yet, go ahead and snag the download <a href="https://www.postman.com/downloads/">here</a>.</p>
<p><em>Note: Much of the code below is minimal and does not contain any error handling. Therefore, it should not be used in a production network. Full example code <a href="https://github.com/0x2142/example-scripts/tree/master/postman-python">here</a>.</em></p>
<hr>
<h2 id="postman-simple-get-request">Postman: Simple GET Request</h2>
<p>So first, let&rsquo;s start off with an example of using Postman for a simple GET request.</p>
<p>In this example, we&rsquo;ll keep things simple &amp; use a non-authenticated API endpoint. We&rsquo;ll accomplish this using a free website called <a href="https://jsonplaceholder.typicode.com/">JSON Placeholder</a>.</p>
<p>Let&rsquo;s go ahead and start up Postman, and we&rsquo;ll see a blank workspace:</p>
<p><img alt="001&mdash;Postman-1" loading="lazy" src="/content/images/2021/03/001---Postman-1.PNG#center"></p>
<p>What we&rsquo;ll need to pay attention to first, is what type of HTTP request we&rsquo;re sending - as well as the URL we want to send our request to. If we expand the request type dropdown, we&rsquo;ll see a handful of options - but we&rsquo;ll only be using a GET request for now:</p>
<p><img alt="002&mdash;Postman" loading="lazy" src="/content/images/2021/03/002---Postman.PNG#center"></p>
<p>In order to create our first request, we&rsquo;ll just need to enter our API endpoint URL. We&rsquo;ll use the <strong>users</strong> endpoint offered by JSON Placeholder, which is located at: <a href="https://jsonplaceholder.typicode.com/users">https://jsonplaceholder.typicode.com/users</a></p>
<p><img alt="003&mdash;Postman" loading="lazy" src="/content/images/2021/03/003---Postman.png#center"></p>
<p>In the screenshot above, you&rsquo;ll see in the highlighted box that we entered our URL. Next, we just click the big <strong>Send</strong> button on the right side.</p>
<p>I already sent the GET request, so in the screenshot you&rsquo;ll also notice that we have our JSON response from the API. The mock data offered by this API provides us with a list of 10 different users, along with the data that&rsquo;s been entered for each user.</p>
<p>What if we only wanted to get data for one specific user? Well, our test API site supports using query parameters to filter our response. Let&rsquo;s say we want to find information for a user named <strong>Delphine</strong>. We would append <em>?username=Delphine</em> to our URL:</p>
<p><img alt="004&mdash;Postman" loading="lazy" src="/content/images/2021/03/004---Postman.PNG#center"></p>
<p>So as we can see above - now our JSON response only contains the data for a single user. If we needed to find a user&rsquo;s phone number, this could be an easy way to quickly filter our data &amp; get to what we need.</p>
<p>This is really where Postman is great. We&rsquo;re able to quickly &amp; visually test out an API call. We can see what the response data looks like, and understand the structure of requests &amp; responses.</p>
<p>But what about when we want to iterate through a number of users and pull only a specific few statistics? Or maybe we need a way to take a list of user information, and automatically upload or convert that data into another system.</p>
<p>For use cases like that, we&rsquo;ll jump over to Python.</p>
<h2 id="python-simple-get-request">Python: Simple GET Request</h2>
<p>In this section, we&rsquo;ll walk through the same example from above - but this time using Python.</p>
<p>First thing we&rsquo;ll need to do, is install the Python <em>requests</em> library. While there are a handful of libraries that can accomplish this work, <em>requests</em> is fairly popular &amp; easy to use.</p>
<p>So we&rsquo;ll kick things off by using pip to install our library:</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">pip</span> <span class="n">install</span> <span class="n">requests</span>
</span></span></code></pre></div><p>Then, we&rsquo;ll create a very simple Python script to perform the same initial GET request from earlier. So we&rsquo;ll pull a list of ALL users from the API.</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">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">URL</span> <span class="o">=</span> <span class="s2">&#34;https://jsonplaceholder.typicode.com/users&#34;</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">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></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span></code></pre></div><p>The code above should be pretty straightforward. First we&rsquo;ll import our <em>requests</em> library. Then, just to keep the code clean, we&rsquo;ll create a variable called <em>URL</em> to hold the URL for the API endpoint.</p>
<p>Next, we send that GET request, using <em>requests.get</em>. Last but not least, we&rsquo;ll go ahead and print out the text payload that we receive back. That output looks pretty similar:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">matt@DESKTOP:~/postman-python$ python3 simpleGET.py 
</span></span><span class="line"><span class="cl"><span class="o">[</span>
</span></span><span class="line"><span class="cl">  <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;id&#34;</span>: 1,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;name&#34;</span>: <span class="s2">&#34;Leanne Graham&#34;</span>,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;username&#34;</span>: <span class="s2">&#34;Bret&#34;</span>,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;email&#34;</span>: <span class="s2">&#34;Sincere@april.biz&#34;</span>,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;address&#34;</span>: <span class="o">{</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;street&#34;</span>: <span class="s2">&#34;Kulas Light&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;suite&#34;</span>: <span class="s2">&#34;Apt. 556&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;city&#34;</span>: <span class="s2">&#34;Gwenborough&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;zipcode&#34;</span>: <span class="s2">&#34;92998-3874&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;geo&#34;</span>: <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;lat&#34;</span>: <span class="s2">&#34;-37.3159&#34;</span>,
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;lng&#34;</span>: <span class="s2">&#34;81.1496&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;phone&#34;</span>: <span class="s2">&#34;1-770-736-8031 x56442&#34;</span>,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;website&#34;</span>: <span class="s2">&#34;hildegard.org&#34;</span>,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;company&#34;</span>: <span class="o">{</span>
</span></span><span class="line"><span class="cl">     -- output truncated --
</span></span></code></pre></div><p>Now, let&rsquo;s add a little extra logic to our next step. Rather than just specifying a static username that we want to search for, let&rsquo;s ask our user to specify a username:</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">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">URL</span> <span class="o">=</span> <span class="s2">&#34;https://jsonplaceholder.typicode.com/users&#34;</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="s2">&#34;Search by Username:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">user</span> <span class="o">=</span> <span class="nb">input</span><span class="p">(</span><span class="s2">&#34;&gt; &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">queryURL</span> <span class="o">=</span> <span class="n">URL</span> <span class="o">+</span> <span class="sa">f</span><span class="s2">&#34;?username=</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s2">&#34;</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">queryURL</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">response</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 made just a few changes. One is printing out a prompt to &ldquo;Search by Username&rdquo;. Then we use the Python <em>input</em> function to collect input from our user, then store that in the <em>user</em> variable.</p>
<p>Next, we create a <em>queryURL</em> - which is similar to the URL we used in the Postman example. Except in this case, we&rsquo;re supplying a dynamic username input based on whatever the end user wants to search for. We use a Python <a href="https://realpython.com/python-f-strings/">f-string</a> to inject the username into our query parameters.</p>
<p>Let&rsquo;s see what that looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">matt@DESKTOP:~/postman-python$ python3 simpleGET.py 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Search by Username:
</span></span><span class="line"><span class="cl">&gt; Delphine
</span></span><span class="line"><span class="cl"><span class="o">[</span>
</span></span><span class="line"><span class="cl">  <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;id&#34;</span>: 9,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;name&#34;</span>: <span class="s2">&#34;Glenna Reichert&#34;</span>,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;username&#34;</span>: <span class="s2">&#34;Delphine&#34;</span>,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;email&#34;</span>: <span class="s2">&#34;Chaim_McDermott@dana.io&#34;</span>,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;address&#34;</span>: <span class="o">{</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;street&#34;</span>: <span class="s2">&#34;Dayna Park&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;suite&#34;</span>: <span class="s2">&#34;Suite 449&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;city&#34;</span>: <span class="s2">&#34;Bartholomebury&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;zipcode&#34;</span>: <span class="s2">&#34;76495-3109&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;geo&#34;</span>: <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;lat&#34;</span>: <span class="s2">&#34;24.6463&#34;</span>,
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;lng&#34;</span>: <span class="s2">&#34;-168.8889&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;phone&#34;</span>: <span class="s2">&#34;(775)976-6794 x41206&#34;</span>,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;website&#34;</span>: <span class="s2">&#34;conrad.com&#34;</span>,
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;company&#34;</span>: <span class="o">{</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;name&#34;</span>: <span class="s2">&#34;Yost and Sons&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;catchPhrase&#34;</span>: <span class="s2">&#34;Switchable contextually-based project&#34;</span>,
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;bs&#34;</span>: <span class="s2">&#34;aggregate real-time technologies&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">  <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">]</span>
</span></span></code></pre></div><p>Perfect! We get the result we expected, which is the single dataset from the user that we specified.</p>
<p>Let&rsquo;s take this one step further! Maybe we want to build a quick utility to search for a user&rsquo;s contact information. We can take the raw JSON output, pull out a few pieces of info, then apply some formatting to make it more user-friendly.</p>
<p>In the following example, we&rsquo;ll update our code to search that JSON response &amp; collect the user&rsquo;s name, email address, and phone number.</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">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">URL</span> <span class="o">=</span> <span class="s2">&#34;https://jsonplaceholder.typicode.com/users&#34;</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="s2">&#34;Search by Username:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">user</span> <span class="o">=</span> <span class="nb">input</span><span class="p">(</span><span class="s2">&#34;&gt; &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">queryURL</span> <span class="o">=</span> <span class="n">URL</span> <span class="o">+</span> <span class="sa">f</span><span class="s2">&#34;?username=</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s2">&#34;</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">queryURL</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">userdata</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">response</span><span class="o">.</span><span class="n">text</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">name</span> <span class="o">=</span> <span class="n">userdata</span><span class="p">[</span><span class="s2">&#34;name&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">email</span> <span class="o">=</span> <span class="n">userdata</span><span class="p">[</span><span class="s2">&#34;email&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">phone</span> <span class="o">=</span> <span class="n">userdata</span><span class="p">[</span><span class="s2">&#34;phone&#34;</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="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2"> can be reached via the following methods:&#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="sa">f</span><span class="s2">&#34;Email: </span><span class="si">{</span><span class="n">email</span><span class="si">}</span><span class="s2">&#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="sa">f</span><span class="s2">&#34;Phone: </span><span class="si">{</span><span class="n">phone</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>So a few things have changed in the above example. First thing you may notice, is that we&rsquo;ve added an additional import: the <strong>json</strong> library. We use this in line 11, where we convert the JSON output into a native python object using the <em>json.loads</em> function.</p>
<p>What this allows us to do is easily pull individual data values from the JSON output. In the original JSON data that we received, we saw a bunch of user data organized into key-value pairs. So once we load that JSON into a Python dictionary, we can pull out individual values and assign them to variables.</p>
<p>Finally - we can print all of that data out to our terminal:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">matt@DESKTOP:~/postman-python$ python3 simpleGET.py
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Search by Username:
</span></span><span class="line"><span class="cl">&gt; Delphine
</span></span><span class="line"><span class="cl">Glenna Reichert can be reached via the following methods:
</span></span><span class="line"><span class="cl">Email: Chaim_McDermott@dana.io
</span></span><span class="line"><span class="cl">Phone: <span class="o">(</span>775<span class="o">)</span>976-6794 x41206
</span></span></code></pre></div><p>Nothing super fancy here - but this could be the beginnings of building out a user search web page, or maybe importing certain data fields into another system. Once we get the basics of working with REST APIs out of the way, there are a ton of possibilities for what can be built.</p>
<h2 id="okay---what-about-network-automation">Okay - What About Network Automation?</h2>
<p>Many new network technologies are now API enabled. Nearly every cloud-hosted platform or management controller offers some method of interacting programmatically. In fact, most routers &amp; switches even offer a REST-based API that can be used for automated configuration or monitoring.</p>
<p>So let&rsquo;s take the following example: We have a couple of Meraki networks, and we&rsquo;ve been tasked with providing a report of how many total clients have connected to our network in the last 30 days. In addition, it would be nice to see a breakdown of the distribution of operating systems.</p>
<p>Let&rsquo;s take a look at the following screenshot from Postman:</p>
<p><img alt="005&mdash;Postman" loading="lazy" src="/content/images/2021/03/005---Postman.png#center"></p>
<p>Using Meraki&rsquo;s <a href="https://developer.cisco.com/meraki/api-v1/#!get-network-clients">API docs</a>, we can determine the exact URL endpoint we need to query. This URL requires that we know the exact network ID for the network we want to get info from, which we&rsquo;ll pretend we already know. Also not shown here, we have an additional HTTP header that includes our API key - which was generated in Meraki Dashboard.</p>
<p>In addition, we have a few query parameters to help make sure we get the data we need. By default, this API endpoint will return 10 devices. In this particular network, we already know we have more than 10 - so we use the <em>perPage</em> query parameter to request up to 100 devices. We also wanted to query the last 30 days of devices, so we use the <em>timespan</em> parameter. This parameter expects the lookback to be in seconds, so we specify 43,200 seconds.</p>
<p>So we got back a list of devices - and specifically in the example shown, it looks like we have an Android device on our network. Easy enough to spot that info, right?</p>
<p>Well - maybe we have more than a handful of devices. We don&rsquo;t want to manually sort through all of that data &amp; assemble a list. Or maybe this task needs to be run more than once. This is where we can use Python to automatically assemble that report with just a few lines of 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">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">URL</span> <span class="o">=</span> <span class="s2">&#34;https://api.meraki.com/api/v1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">APIKEY</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;X-Cisco-Meraki-API-Key&#34;</span><span class="p">:</span> <span class="s2">&#34;xxxxxxxxxxxxxxxxx&#34;</span><span class="p">}</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">def</span> <span class="nf">getClients</span><span class="p">(</span><span class="n">orgID</span><span class="p">,</span> <span class="n">networkList</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    Query clients for each network, return client list
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">clientCount</span> <span class="o">=</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Query Parameters: Return up to 100 devices seen in the past 43,200 seconds (30 days)</span>
</span></span><span class="line"><span class="cl">    <span class="n">q</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&#34;perPage&#34;</span><span class="p">:</span> <span class="s2">&#34;100&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">         <span class="s2">&#34;timespan&#34;</span><span class="p">:</span> <span class="s2">&#34;43200&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">network</span> <span class="ow">in</span> <span class="n">networkList</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Query clients for each network</span>
</span></span><span class="line"><span class="cl">        <span class="n">queryURL</span> <span class="o">=</span> <span class="n">URL</span> <span class="o">+</span> <span class="sa">f</span><span class="s2">&#34;/networks/</span><span class="si">{</span><span class="n">network</span><span class="si">}</span><span class="s2">/clients&#34;</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">queryURL</span><span class="p">,</span> <span class="n">params</span><span class="o">=</span><span class="n">q</span><span class="p">,</span> <span class="n">headers</span><span class="o">=</span><span class="n">APIKEY</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">loads</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Grab client OS from each device &amp; append to clientCount dictionary</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">client</span> <span class="ow">in</span> <span class="n">data</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">clientCount</span><span class="p">[</span><span class="n">client</span><span class="p">[</span><span class="s2">&#34;os&#34;</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">clientCount</span><span class="p">[</span><span class="n">client</span><span class="p">[</span><span class="s2">&#34;os&#34;</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">TypeError</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="cl">            <span class="n">total</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Append final count of all devices &amp; return dict</span>
</span></span><span class="line"><span class="cl">    <span class="n">clientCount</span><span class="p">[</span><span class="s2">&#34;Total Devices&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">total</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">clientCount</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">printReport</span><span class="p">(</span><span class="n">clientOS</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">    Print final output to terminal
</span></span></span><span class="line"><span class="cl"><span class="s2">    &#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Count of clients by operating system:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">OS</span> <span class="ow">in</span> <span class="n">clientOS</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">OS</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">clientOS</span><span class="p">[</span><span class="n">OS</span><span class="p">]</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</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></code></pre></div><p>To keep things simple - we&rsquo;ll only look at a snippet of the code. The entire example will be posted to <a href="https://github.com/0x2142/example-scripts/tree/master/postman-python">GitHub</a>, and contains two additional functions that will automatically find our Organization ID &amp; Network ID.</p>
<p>In our <em>getClients</em> function, we create an empty Python dictionary to hold our list of client operating systems - and we also create a <em>total</em> variable which we&rsquo;ll increment for every client in the list.</p>
<p>The HTTP GET request should look fairly similar to what we used previously. The big difference is that we&rsquo;re creating a dictionary called <em>q</em> to hold our <em>perPage</em> &amp; <em>timespan</em> query parameters. Then we include that in our GET request by adding <em>params=q</em> to the request.</p>
<p>Next - we convert the JSON response into a Python object, and walk through every device in that list. For each device in the list, we look at the &ldquo;os&rdquo; value to determine the operating system detected by Meraki. We then use a try/except block to see if we can increment the counter for that operating system. If we get a <strong>KeyError</strong>, then we know the OS isn&rsquo;t currently on the list &amp; we can just add it with a new value of 1.</p>
<p>After all that has been completed, we return our <em>clientCount</em> dictionary - which our primary function passes to the <em>printReport</em> function. This just prints out a header, then iterates through our <em>clientCount</em> dictionary and prints each operating system &amp; count.</p>
<p>Let&rsquo;s take a look at that output below:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">matt@DESKTOP:~/postman-python$ python getOScount.py
</span></span><span class="line"><span class="cl">Count of clients by operating system:
</span></span><span class="line"><span class="cl">None: <span class="m">9</span>
</span></span><span class="line"><span class="cl">Sailfish OS: <span class="m">2</span>
</span></span><span class="line"><span class="cl">Nokia: <span class="m">1</span>
</span></span><span class="line"><span class="cl">Windows 10: <span class="m">2</span>
</span></span><span class="line"><span class="cl">Raspberry Pi: <span class="m">2</span>
</span></span><span class="line"><span class="cl">Android: <span class="m">4</span>
</span></span><span class="line"><span class="cl">Meraki OS: <span class="m">1</span>
</span></span><span class="line"><span class="cl">Total Devices: <span class="m">21</span>
</span></span></code></pre></div><p>In the list above, we can see that we have a total of 21 devices in our network - and 7 different operating systems were found as well.</p>
<p>That output may not be the prettiest of reports, but it was accomplished with ~50 lines of Python &amp; takes less than a few seconds to run. If we wanted to format the data in another way, or include data from a non-Meraki source, we absolutely could.</p>
<p>That&rsquo;s part of what&rsquo;s exciting about using Python for network automation - most anything we could think of doing is possible.</p>
<h2 id="tip-use-postman-to-generate-python-code">TIP: Use Postman to Generate Python Code</h2>
<p>Okay - so I wanted to save this bit for last. Now that you&rsquo;ve seen examples in both tools, I wanted to make sure we cover a feature in postman that can save some time.</p>
<p>So over in the right-hand side of your Postman window, you&rsquo;ll see an icon that looks like this: <strong>&lt;/&gt;</strong></p>
<p><img alt="006&mdash;Postman" loading="lazy" src="/content/images/2021/03/006---Postman.png#center"></p>
<p>If you click that, you&rsquo;ll see a dropdown menu of various languages &amp; tools. Select one of those options and Postman will auto-generate code you can use. This auto-generated code will execute the same request you have built in the Postman GUI.</p>
<p>For example, our last Postman request we used to get Meraki clients:</p>
<p><img alt="007&mdash;Postman" loading="lazy" src="/content/images/2021/03/007---Postman.png#center"></p>
<p>I wanted to save this for last, because I feel like it&rsquo;s important to understand how to write the code manually first - then take advantages of shortcuts after having a good understanding of what the code is doing.</p>
<hr>
<p>That&rsquo;s about all I had for this example. Again, my intent here wasn&rsquo;t to create an all-inclusive resource for how to use Postman &amp; Python - but rather to give some examples for each one &amp; show where/why each would be used.</p>
<p>I&rsquo;ve met a handful of people over the years who have seen many API demos using Postman, but aren&rsquo;t sold yet on the value of learning Python &amp; getting into network automation.</p>
<p>Hopefully these examples help bridge that gap a little bit 😄</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>
  </channel>
</rss>
