Dusting off the Tool Box
Posted on May 14, 2025 by Michael Keane GallowayI’ve often had the idea of having a git repository to just store odds and ends;
tools that I’ve made; scripts etc. While setting up my latest Linux install, I
rediscovered my tool_box
git repository. I’ve mainly started using it as a
way to share my new neovim configuration between work and home. That’s said, I
found an interesting tool that I left in there back in 2017.
As a bit of back story, 2017 was the year that LoopNet fully integrated with CoStar’s database. There were quite a few things in motion, to get that task down, and one of the items that I was responsible for interacted with a database table that stored IPV4 addresses as unsigned 32 bit integers. I had recently read the K&R C book, so I thought it would be fun to write a quick tool in C to convert between the typical IPV4 string format and an unsigned integer. The following is the tool that I wrote.
Anatomy of iptool
In my iptool
directory of the tool_box
, I had the following files:
ipt.h
ipt.c
ipt.test.c
ipv4inttostr.c
ipv4strtoint.c
makefile
We have a header file to specify all of the functions within the tool, a C file
to house the main functionality of the tool, unit tests in a .test.c
file, a
C file for each conversion function, and a make file to be able to build/test
the tool. The first thing that I did was when I rediscovered this was to build
the tool and run a couple of know IP addresses through the resulting binary.
Everything seemed to be in working order. I then tried to run the unit tests,
and I think my current version of Linux is missing some .so
files for the
unit testing framework.
The main C file
For fun, and since I’m not really going to switch my tool_box
to public, I
thought I’d start listing out some of the code and discussing what I see in it
about 7 years after writing it. Starting of course with ipt.c
where we have
our main function.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "ipt.h"
void usage() {
(
printf"ipt <option> <value>\n"
" -c IP address to decimal value.\n"
" -f Decimal value to IP address.\n"
);
}
void main(int argc, char** argv) {
char* value = NULL;
char resBuffer[__IP_MIN_BUFFER_LENGTH__] = "";
int option = getopt(argc, argv, "c:f:");
switch(option) {
case 'c':
= optarg;
value ("%u\n", ipv4strtoint(value, strlen(value)));
printfbreak;
case 'f':
= optarg;
value (strtoul(value, NULL, 0), resBuffer, __IP_MIN_BUFFER_LENGTH__);
ipv4inttostr("%s\n", resBuffer);
printfbreak;
default:
();
usage}
}
One of the things that I really like about this code is that it has a simple
command line switching approach. I’m using getopt
. There’s only really two
switches -c
gets you the IP to decimal conversion, and -f
does the
opposite.
It looks like I had a buffer length constant that I was using to manage the
buffer for the IP address. It was probably done to make sure that I’m
rationally handling memory, but I’m not too certain that I’m handling this in a
sufficient way. For example, I’m calling strlen
and stttoul
without using
the buffer length to limit the examination of the buffer value
to a known
safe buffer size. That could cause bugs or en exploitable bug.
IP to Int
The following code is used to convert an IPv4 address to an integer. It does
this by building by loading a substring from the current position within the
string to the next .
in the IP address. Then that temporary string is
converted to an integer, shifted for the appropriate bit length for the octet,
and bitwise or’d into the resulting unsigned int.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "ipt.h"
#define __TEMP_BUFF_SIZE__ 4
unsigned int ipv4strtoint(const char* str, int length) {
unsigned int result = 0;
int shift = 24;
char temp[__TEMP_BUFF_SIZE__] = "";
(NULL != str);
assert(__IP_MIN_STR_LENGTH__ <= length);
assert
for(int i = 0; i < length && shift >= 0; i++, shift -= 8) {
for(int k = 0; i < length && k < __TEMP_BUFF_SIZE__ && str[i] != '.'; i++, k++) {
[k] = str[i];
temp}
|= atoi(temp) << shift;
result
(temp, 0, sizeof(temp));//this could get optimized out...
memset}
return result;
}
Looking back on this code, I’m wondering why I used i
and k
as counter
variables instead of i
and j
? My initial guess is that the i
and j
might have looked too similar in my editor at the time. That counter variable
issue also gave me the impression that I was building out the split of the
input string. I’m not much of a C programmer, but looking at this, I’m left
wondering if there’s a standard library implementation string split that I
could have used to simplify my work.
Then for that comment, I believe there was some worry that zeroing out an array would get optimized out. I think I had heard about a class of security bugs in C where that happened. In retrospect, it’s kind of silly to worry about this in a simple tool that I built. I don’t think I ever compiled this with optimization flags, so this would have never happened. So 7 plus years later, I have found a liar’s comment.
Int to IP
The following function builds a string representation of an IPv4 address from
an unsigned 32 bit integer. It does this by extracting each octet using a
bitwise and between a mask and the unsigned int storing the IP address. After
the octet is extracted the mask is shifted to get the next octet. The octet is
then written to the output buffer using snprintf
. The size of the octet is
used to determine how to adjust the string pointer and length of allowed
characters to properly use snprintf
.
#include <stdio.h>
#include <assert.h>
#include "ipt.h"
void ipv4inttostr(unsigned int ip, char* str, int length) {
(NULL != str);
assert(__IP_MIN_BUFFER_LENGTH__ <= length);
assert
unsigned int mask = 0xff000000;
int shift = 24;
while(shift >= 0) {
unsigned char b = (mask & ip) >> shift;
= mask >> 8;
mask -= 8;
shift
if (shift != -8) {
(str, length, "%u.", b);
snprintf}
else {
(str, length, "%u", b);
snprintf}
int adj = 0;
if (b >= 100) {
= 4;
adj }
else if (b >= 10) {
= 3;
adj }
else {
= 2;
adj }
+= adj;
str -= adj;
length }
}
Overall I really like this function. Years later, I can still figure out what
it does. The only part that took me a little bit of effort to understand was
calculating the adjustment for the str
pointer and length.
The Unit Tests
The following code block contains the unit tests that I wrote for this project. I know it’s likely overkill to write unit tests for such a small personal project, but I remember learning a lot about how tests work in C. I had never had to use a unit testing library for C while in school, and I thought it would be a neat learning experience.
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#include "ipt.h"
static void ipv4inttostr_NULL_expectFailure(void **state) {
(void) state;
(ipv4inttostr(1345678, NULL, 0));
expect_assert_failure}
static void ipv4inttostr_emptyStr_expectFailure(void **state) {
(void) state;
(ipv4inttostr(1345678, "", 0));
expect_assert_failure}
static void ipv4inttostr_0_expect0s(void **state) {
(void) state;
char buffer[__IP_MIN_BUFFER_LENGTH__] = "";
char expected[] = "0.0.0.0";
(0, buffer, __IP_MIN_BUFFER_LENGTH__);
ipv4inttostr
(buffer, expected);
assert_string_equal}
static void ipv4inttostr_dec_expected(void **state) {
(void) state;
char buffer[__IP_MIN_BUFFER_LENGTH__] = "";
char expected[] = "2.119.4.14";
(41354254, buffer, __IP_MIN_BUFFER_LENGTH__);
ipv4inttostr
(buffer, expected);
assert_string_equal}
static void ipv4strtoint_NULL_expectFailure(void **state) {
(void) state;
(ipv4strtoint(NULL, 0));
expect_assert_failure}
static void ipv4strtoint_empty_expectFailure(void **state) {
(void) state;
(ipv4strtoint("", 0));
expect_assert_failure}
static void ipv4strtoint_home_dec(void **state) {
(void) state;
int expected = 2130706433;
(ipv4strtoint("127.0.0.1", 9), expected);
assert_int_equal}
static void ipv4strtoint_loopback_dec(void **state) {
(void) state;
int expected = 0;
(ipv4strtoint("0.0.0.0", 8), expected);
assert_int_equal}
static void ipv4strtoint_router_dec(void **state) {
(void) state;
unsigned int expected = 3232235777;
unsigned int result = ipv4strtoint("192.168.1.1", 12);
(expected == result);
assert_true}
static void crossconfirm_home_passes(void **state) {
(void) state;
char buffer[__IP_MIN_BUFFER_LENGTH__] = "";
char expected[] = "127.0.0.1";
unsigned int intermediate = ipv4strtoint(expected, 9);
(intermediate, buffer, __IP_MIN_BUFFER_LENGTH__);
ipv4inttostr
(expected, buffer);
assert_string_equal}
static void crossconfirm_router_passes(void **state) {
(void) state;
char buffer[__IP_MIN_BUFFER_LENGTH__] = "";
char expected[] = "192.168.1.1";
unsigned int intermediate = ipv4strtoint(expected, 12);
(intermediate, buffer, __IP_MIN_BUFFER_LENGTH__);
ipv4inttostr
(expected, buffer);
assert_string_equal}
int main(void) {
const struct CMUnitTest tests[] = {
(ipv4inttostr_NULL_expectFailure),
cmocka_unit_test(ipv4inttostr_emptyStr_expectFailure),
cmocka_unit_test(ipv4inttostr_0_expect0s),
cmocka_unit_test(ipv4inttostr_dec_expected),
cmocka_unit_test(ipv4strtoint_NULL_expectFailure),
cmocka_unit_test(ipv4strtoint_empty_expectFailure),
cmocka_unit_test(ipv4strtoint_home_dec),
cmocka_unit_test(ipv4strtoint_loopback_dec),
cmocka_unit_test(ipv4strtoint_router_dec),
cmocka_unit_test(crossconfirm_home_passes),
cmocka_unit_test(crossconfirm_router_passes),
cmocka_unit_test};
return cmocka_run_group_tests(tests, NULL, NULL);
}
I can definitely see the influence of how I learned to write unit tests for
C#
. I often have my unit tests named in the convention
<function_name>_<scenario_description>_<expected_outcome>
. Other than that,
not much else sticks out to me about these unit tests. I’m sure there could be
better coverage, but everything is pretty simple and straightforward, so this
probably is more than adequate for a practice tool that I wrote.
The Future of my tool_box
I recently started stashing configurations that I want to have on multiple
machines in my tool_box
repository. Mainly configurations for tools like
nvim
. I think I’ll continue to have it house configurations and some
convenience shell scripts. Anything that I would want to be public will
probably get spun out into a public repository.