<p><div><div><div><div data-width="100" data-show-faces="false" data-send="true" data-layout="button_count" data-height="21" data-href="http://www.r-bloggers.com/slaying-cidr-orcs-with-triebeard-a-k-a-fast-trie-based-ipv4-in-cidr-lookups-in-r/"></div></div><div><a data-url="http://www.r-bloggers.com/slaying-cidr-orcs-with-triebeard-a-k-a-fast-trie-based-ipv4-in-cidr-lookups-in-r/" data-counturl="http://www.r-bloggers.com/slaying-cidr-orcs-with-triebeard-a-k-a-fast-trie-based-ipv4-in-cidr-lookups-in-r/" data-count="horizontal" data-text="Slaying CIDR Orcs with Triebeard (a.k.a. fast trie-based ‘IPv4-in-CIDR’ lookups in R)" class="twitter-share-button" data-via="rbloggers" href="https://twitter.com/share"></a></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="http://rud.is/b/2016/07/12/slaying-cidr-orcs-with-triebeard-a-k-a-fast-trie-based-ipv4-in-cidr-lookups-in-r/"> R – rud.is</a></strong>, and kindly contributed to <a rel="nofollow" href="http://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>The insanely productive elf-lord, <a target="_blank" href="http://twitter.com/quominus">@quominus</a> put together a small package (<a target="_blank" href="https://github.com/ironholds/triebeard"><code>triebeard</code></a>) that exposes an API for <a target="_blank" href="https://en.wikipedia.org/wiki/Trie">radix/prefix tries</a> at both the R and Rcpp levels. I know he had some personal needs for this and we both kinda need these to augment some functions in our <code>iptools</code> package. Despite <code>triebeard</code> having both a vignette and function-level examples, I thought it might be good to show a real-world use of the package (at least in the cyber real world): fast determination of which <a target="_blank" href="https://en.wikipedia.org/wiki/Autonomous_system_(Internet)">autonomous system</a> an IPv4 address is in (if it’s in one at all).</p>
<p>I’m not going to delve to deep into routing (you can find a good primer <a target="_blank" href="http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=81619&site_id=1#import">here</a> and one that puts routing in the context of radix tries <a target="_blank" href="http://www.juniper.net/documentation/en_US/junos14.1/topics/usage-guidelines/policy-configuring-route-lists-for-use-in-routing-policy-match-conditions.html">here</a>) but there exists, essentially, abbreviated tables of which IP addresses belong to a particular network. These tables are in routers on your local networks and across the internet. Groups of these networks (on the internet) are composed into those autonomous systems I mentioned earlier and these tables are used to get the packets that make up the cat videos you watch routed to you as efficiently as possible.</p>
<p>When dealing with cybersecurity data science, it’s often useful to know which autonomous system an IP address belongs in. The world is indeed full of peril and in it there are many dark places. It’s a dangerous business, going out on the internet and we sometimes find it possible to identify unusually malicious autonomous systems by looking up suspicious IP addresses en masse. These mappings look something like this:</p>
<pre><code>CIDR            ASN
1.0.0.0/24      47872
1.0.4.0/24      56203
1.0.5.0/24      56203
1.0.6.0/24      56203
1.0.7.0/24      38803
1.0.48.0/20     49597
1.0.64.0/18     18144</code></pre>
<p>Each CIDR has a start and end IP address which can ultimately be converted to integers. Now, one <em>could</em> just sequentially compare start and end ranges to see which CIDR an IP address belongs in, but there are (as of the day of this post) <code>647,563</code> CIDRs to compare against, which—in the worst case—would mean having to traverse through the entire list to find the match (or discover there is no match). There are some trivial ways to slightly optimize this, but the search times could still be fairly long, especially when you’re trying to match a billion IPv4 addresses to ASNs.</p>
<p>By storing the CIDR mask (the number of bits of the leading IP address specified after the <code>/</code>) in binary form (strings of 1’s and 0’s) as keys for the trie, we get much faster lookups (only a few comparisons at worst-case vs 647,563).</p>
<p>I made an initial, naïve, mostly straight R, implementation as a precursor to a more low-level implementation in Rcpp in our <code>iptools</code> package and to illustrate this use of the <code>triebeard</code> package.</p>
<p>One thing we’ll need is a function to convert an IPv4 address (in long integer form) into a binary character string. We <em>could</em> do this with base R, but it’ll be super-slow and it doesn’t take much effort to create it with an Rcpp inline function:</p>
<pre><code>library(Rcpp)
library(inline)

ip_to_binary_string &lt;- rcpp(signature(x=&quot;integer&quot;), &quot;
  NumericVector xx(x);

  std::vector&lt;double> X(xx.begin(),xx.end());
  std::vector&lt;std::string> output(X.size());

  for (unsigned int i=0; i&lt;X.size(); i++){

    if ((i % 10000) == 0) Rcpp::checkUserInterrupt();

    output[i] = std::bitset&lt;32>(X[i]).to_string();

  }

  return(Rcpp::wrap(output));
&quot;)

ip_to_binary_string(ip_to_numeric(&quot;192.168.1.1&quot;))
## [1] &quot;11000000101010000000000100000001&quot;</code></pre>
<p>We take a vector from R and use some C++ standard library functions to convert them to bits. I vectorized this in C++ for speed (which is just a fancy way to say I used a <code>for</code> loop). In this case, our short cut will not make for a long delay.</p>
<p>Now, we’ll need a CIDR file. There are <a target="_blank" href="http://data.4tu.nl/repository/uuid:d4d23b8e-2077-4592-8b47-cb476ad16e12">historical ones</a> avaialble, and I use one that I generated the day of this post (and, referenced in the code block below). You can use <a target="_blank" href="https://github.com/hadiasghari/pyasn"><code>pyasn</code></a> to make new ones daily (relegating mindless, automated, menial data retrieval tasks to the python goblins, like one should).</p>
<pre><code>library(iptools)
library(stringi)
library(dplyr)
library(purrr)
library(readr)
library(tidyr)

asn_dat_url &lt;- &quot;http://rud.is/dl/asn-20160712.1600.dat.gz&quot;
asn_dat_fil &lt;- basename(asn_dat_url)
if (!file.exists(asn_dat_fil)) download.file(asn_dat_url, asn_dat_fil)

rip &lt;- read_tsv(asn_dat_fil, comment=&quot;;&quot;, col_names=c(&quot;cidr&quot;, &quot;asn&quot;))
rip %>%
  separate(cidr, c(&quot;ip&quot;, &quot;mask&quot;), &quot;/&quot;) %>%
  mutate(prefix=stri_sub(ip_to_binary_string(ip_to_numeric(ip)), 1, mask)) -> rip_df

rip_df
## # A tibble: 647,557 x 4
##           ip  mask   asn                   prefix
##        &lt;chr> &lt;chr> &lt;int>                    &lt;chr>
## 1    1.0.0.0    24 47872 000000010000000000000000
## 2    1.0.4.0    24 56203 000000010000000000000100
## 3    1.0.5.0    24 56203 000000010000000000000101
## 4    1.0.6.0    24 56203 000000010000000000000110
## 5    1.0.7.0    24 38803 000000010000000000000111
## 6   1.0.48.0    20 49597     00000001000000000011
## 7   1.0.64.0    18 18144       000000010000000001
## 8  1.0.128.0    17  9737        00000001000000001
## 9  1.0.128.0    18  9737       000000010000000010
## 10 1.0.128.0    19  9737      0000000100000000100
## # ... with 647,547 more rows</code></pre>
<p>You can save off that <code>data_frame</code> to an R data file to pull in later (but it’s pretty fast to regenerate).</p>
<p>Now, we create the trie, using the prefix we calculated and a value we’ll piece together for this example:</p>
<pre><code>library(triebeard)

rip_trie &lt;- trie(rip_df$prefix, sprintf(&quot;%s/%s|%s&quot;, rip_df$ip, rip_df$mask, rip_df$asn))</code></pre>
<p>Yep, that’s it. If you ran this yourself, it should have taken less than 2s on most modern systems to create the nigh 700,000 element trie.</p>
<p>Now, we’ll generate a million random IP addresses and look them up:</p>
<pre><code>set.seed(1492)
data_frame(lkp=ip_random(1000000),
           lkp_bin=ip_to_binary_string(ip_to_numeric(lkp)),
           long=longest_match(rip_trie, lkp_bin)) -> lkp_df

lkp_df
## # A tibble: 1,000,000 x 3
##               lkp                          lkp_bin                long
##             &lt;chr>                            &lt;chr>               &lt;chr>
## 1   35.251.195.57 00100011111110111100001100111001  35.248.0.0/13|4323
## 2     28.57.78.42 00011100001110010100111000101010                &lt;NA>
## 3   24.60.146.202 00011000001111001001001011001010   24.60.0.0/14|7922
## 4    14.236.36.53 00001110111011000010010000110101                &lt;NA>
## 5   7.146.253.182 00000111100100101111110110110110                &lt;NA>
## 6     2.9.228.172 00000010000010011110010010101100     2.9.0.0/16|3215
## 7  108.111.124.79 01101100011011110111110001001111 108.111.0.0/16|3651
## 8    65.78.24.214 01000001010011100001100011010110   65.78.0.0/19|6079
## 9   50.48.151.239 00110010001100001001011111101111   50.48.0.0/13|5650
## 10  97.231.13.131 01100001111001110000110110000011   97.128.0.0/9|6167
## # ... with 999,990 more rows</code></pre>
<p>On most modern systems, that should have taken less than 3s.</p>
<p>The <code>NA</code> values are not busted lookups. Many IP networks are assigned but not accessible (see <a target="_blank" href="https://en.wikipedia.org/wiki/List_of_assigned_/8_IPv4_address_blocks">this</a> for more info). You can validate this with <code>cymruservices::bulk_origin()</code> on your own, too).</p>
<p>The trie structure for these CIDRs takes up approximately 9MB of RAM, a small price to pay for speedy lookups (and, memory really is not what the heart desires, anyway). Hopefully the <code>triebeard</code> package will help you speed up your own lookups and stay-tuned for a new version of <code>iptools</code> with some new and enhanced functions.</p>
<p><div><div><div><div data-width="55" data-show-faces="false" data-send="true" data-layout="box_count" data-height="62" data-href="http://www.r-bloggers.com/slaying-cidr-orcs-with-triebeard-a-k-a-fast-trie-based-ipv4-in-cidr-lookups-in-r/"></div></div><div><a data-url="http://www.r-bloggers.com/slaying-cidr-orcs-with-triebeard-a-k-a-fast-trie-based-ipv4-in-cidr-lookups-in-r/" data-counturl="http://www.r-bloggers.com/slaying-cidr-orcs-with-triebeard-a-k-a-fast-trie-based-ipv4-in-cidr-lookups-in-r/" data-count="vertical" data-text="Slaying CIDR Orcs with Triebeard (a.k.a. fast trie-based ‘IPv4-in-CIDR’ lookups in R)" class="twitter-share-button" data-via="rbloggers" href="https://twitter.com/share"></a></div><div></div></div><div></div></div>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="http://rud.is/b/2016/07/12/slaying-cidr-orcs-with-triebeard-a-k-a-fast-trie-based-ipv4-in-cidr-lookups-in-r/"> R – rud.is</a></strong>.</div>
<hr>
<a rel="nofollow" href="http://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="http://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a title="The R Project for Statistical Computing" rel="nofollow" href="http://www.r-project.org/">R</a> news and <a title="R tutorials" rel="nofollow" href="http://www.r-bloggers.com/?s=tutorial">tutorials</a> on topics such as: <a title="Data science" rel="nofollow" href="http://www.r-bloggers.com/?s=data%20science">Data science</a>, <a title="Big Data" rel="nofollow" href="http://www.r-bloggers.com/?s=Big%20Data">Big Data, <a title="R jobs" rel="nofollow" href="http://www.r-users.com/">R jobs</a>, visualization (<a title="ggplot and ggplot2 tutorials" rel="nofollow" href="http://www.r-bloggers.com/?s=ggplot2">ggplot2</a>, <a title="Boxplots using lattice and ggplot2 tutorials" rel="nofollow" href="http://www.r-bloggers.com/?s=boxplot">Boxplots</a>, <a title="Maps and gis" rel="nofollow" href="http://www.r-bloggers.com/?s=map">maps</a>, <a title="Animation in R" rel="nofollow" href="http://www.r-bloggers.com/?s=animation">animation</a>), programming (<a title="RStudio IDE for R" rel="nofollow" href="http://www.r-bloggers.com/?s=RStudio">RStudio</a>, <a title="Sweave and literate programming" rel="nofollow" href="http://www.r-bloggers.com/?s=sweave">Sweave</a>, <a title="LaTeX in R" rel="nofollow" href="http://www.r-bloggers.com/?s=LaTeX">LaTeX</a>, <a title="SQL and databases" rel="nofollow" href="http://www.r-bloggers.com/?s=SQL">SQL</a>, <a title="Eclipse IDE for R" rel="nofollow" href="http://www.r-bloggers.com/?s=eclipse">Eclipse</a>, <a title="git and github, Version Control System" rel="nofollow" href="http://www.r-bloggers.com/?s=git">git</a>, <a title="Large data in R using Hadoop" rel="nofollow" href="http://www.r-bloggers.com/?s=hadoop">hadoop</a>, <a title="Web Scraping of google, facebook, yahoo, twitter and more using R" rel="nofollow" href="http://www.r-bloggers.com/?s=Web+Scraping">Web Scraping</a>) statistics (<a title="Regressions and ANOVA analysis tutorials" rel="nofollow" href="http://www.r-bloggers.com/?s=regression">regression</a>, <a title="principal component analysis tutorial" rel="nofollow" href="http://www.r-bloggers.com/?s=PCA">PCA</a>, <a title="Time series" rel="nofollow" href="http://www.r-bloggers.com/?s=time+series">time series</a>, <a title="finance trading" rel="nofollow" href="http://www.r-bloggers.com/?s=trading">trading</a>) and more...
<img height="1" alt="" width="1" src="http://feeds.feedburner.com/~r/RBloggers/~4/akQitb_9z7g"></a></div>