Creating custom exit codes, or alternative response data

My goal is grab the public IP address of an endpoint, using just a fixlet. I know this can be done using a fixlet+analysis combo, but I’m wondering if what I’m attempting is possible.

I have a fixlet that stores the public IP address in a text file:

download now as ip.txt http://api.ipify.org

and I’d like to return the IP address as the exit code. However, I run into the following problems:

  1. I can’t just return the string, because the exit code needs to be numerical. This rules out something like exit {line of file ip.txt} or exit {line of file ip.txt as integer}
  2. I can’t convert the IP address to an integer, because the resultant number is one character too long to be passed as an exit code value, eg. 3232235521 (192.168.0.1) gives error “Exit code is not a number”, but 323223552 does not.

So, I’m wondering if it’s possible to a) pass a string as the exit code, b) increase the allowable exit code length, c) if there’s another response field, like the exit code, that I can leverage, or d) if there’s another way entirely to grab the public IP address of an endpoint in one action, across any platform.

I’m fairly certain that this is absolutely not what BigFix is for, and that I’m misusing the tools, but at this point I’m just curious if it’s possible.

That’s…a real interesting use-case. You’re correct, the right way to do this is to output the result to a file and bring it back to the server in an Analysis.

That said, the problem with abusing the exit code this way isn’t too many digits, but too many bits. The exit code is a 32-bit signed integer. The way a computer encodes that is the leftmost bit is a “sign” bit, representing 0 for positive or 1 for a negative number. That’s what so many exit code values intending to be something like 0xFFFF0001 end up being displayed as a large negative number - the leftmost bit being set indicates the number should be negative when viewed as an integer.

The end result of that is you only have 31 bits to represent the number, plus the bit to represent it’s sign.

The algorithm in which negative numbers are stored is called “Two’s Complement”. You can Google that for some interesting internals on how binary values represent integers. You can return negative numbers as an exit code, so there’s probably a path if you calculate the “Two’s Complement” of the IP address.

I think there’s probably a path there by going through bit sets or hexadecimal string to integer, but I’m typing on my phone now and can’t really explore it at the moment.

This relevance challenge from February 2020 has a lot of info about converting an IP Address string into a bit set, it may be helpful to start

1 Like

There’s an idea for that!

https://bigfix-ideas.hcltechsw.com/ideas/BFP-I-86

Thanks for the tips, @JasonWalker

It’s not pretty, but I was able to use the two’s complement to return an exit code that can later be converted to an IP address.

download now as ip.txt http://api.ipify.org

parameter "address"="{line 1 of file "__Download/ip.txt"}"
parameter "first_block" = "{preceding text of first "." of parameter "address"}"
parameter "first_excess" = "{following text of first "." of parameter "address"}"
parameter "second_block" = "{preceding text of first "." of parameter "first_excess"}"
parameter "second_excess" = "{following text of first "." of parameter "first_excess"}"
parameter "third_block" = "{preceding text of first "." of parameter "second_excess"}"
parameter "fourth_block" = "{following text of first "." of parameter "second_excess"}"

parameter "first_uint" = "{parameter "first_block" as integer *256*256*256}"
parameter "second_uint" = "{parameter "second_block" as integer *256*256}"
parameter "third_uint" = "{parameter "third_block" as integer *256}"

parameter "uint" = "{parameter "first_uint" as integer + parameter "second_uint" as integer + parameter "third_uint" as integer + parameter "fourth_block" as integer}"

parameter "binary" = "{integer (parameter "uint") as bit set}"

if {bit 31 of bit set (parameter "binary")}

	parameter "binary_flip1" = "{concatenation "." of (substrings separated by "0" of (parameter "binary"))}"
	parameter "binary_flip2" = "{concatenation "0" of (substrings separated by "1" of (parameter "binary_flip1"))}"
	parameter "binary_flip3" = "{concatenation "1" of (substrings separated by "." of (parameter "binary_flip2"))}"

	parameter "twos_comp" = "{bit set (parameter "binary_flip3") as integer + 1}"

	parameter "answer" = "{(parameter "twos_comp") as integer * -1}"

else

	parameter "answer" = "{parameter "uint"}"

endif

exit {(parameter "answer") as integer}

So if your IP is 192.168.1.1, the exit code will be -1062731519.

You can then convert the signed 32-bit int to an unsigned 32-bit int, 3232235777, and convert it back into an IP address.

¯\_(ツ)_/¯ Neat

Edit: I incorrectly assumed the answer would always be negative — added a conditional to check.

3 Likes

Very neat! I love these kinds of relevance puzzles.
Tagging @brolly33

Already on it. just trying to remember how to convert a string back into bits and back into an integer. Should be done after the Webinar today :wink:

I haven’t tested all cases yet, but I think there are some shortcuts we can take.

First, breaking up IP addresses into its component parts:

q: (tuple string item 0 of it as integer, tuple string item 1 of it as integer, tuple string item 2 of it as integer, tuple string item 3 of it as integer) of (tuple string of substrings separated by "." of it) of ("192.168.0.1"; "128.168.0.1"; "10.168.0.1"; "1.2.3.4")
A: 192, 168, 0, 1
A: 128, 168, 0, 1
A: 10, 168, 0, 1
A: 1, 2, 3, 4
T: 0.602 ms

Second, we can convert those addresses into hexadecimal and show their bitset representations:

q: (item 0 of it as integer as hexadecimal, item 0 of it, item 1 of it) of (left shift 24 of (item 0 of it as bits) + left shift 16 of (item 1 of it as bits) + left shift 8 of (item 2 of it as bits) + (item 3 of it as bits), it) of (tuple string item 0 of it as integer, tuple string item 1 of it as integer, tuple string item 2 of it as integer, tuple string item 3 of it as integer) of (tuple string of substrings separated by "." of it) of ("192.168.0.1"; "128.168.0.1"; "10.168.0.1"; "1.2.3.4")
A: c0a80001, 11000000101010000000000000000001, ( 192, 168, 0, 1 )
A: 80a80001, 10000000101010000000000000000001, ( 128, 168, 0, 1 )
A: aa80001, 1010101010000000000000000001, ( 10, 168, 0, 1 )
A: 1020304, 1000000100000001100000100, ( 1, 2, 3, 4 )
T: 1.161 ms
I: plural ( string, bit set, ( integer, integer, integer, integer ) )

Now, the shortcut part. If ‘bit 31’ is not set, this would be regarded as a “positive” 32-bit integer. If bit 31 is set, we would have to represent this as a negative integer. The shortcut that I think works is to subtract this from 0, and then again set bit 31. Here we don’t really need to show the hexadecimal values, I’m just leaving it in for illustration:

q: (item 0 of it as integer as hexadecimal, (if not bit 31 of it then it as integer else 0 - (it as integer) of it) of item 0 of it) of (left shift 24 of (item 0 of it as bits) + left shift 16 of (item 1 of it as bits) + left shift 8 of (item 2 of it as bits) + (item 3 of it as bits), it) of (tuple string item 0 of it as integer, tuple string item 1 of it as integer, tuple string item 2 of it as integer, tuple string item 3 of it as integer) of (tuple string of substrings separated by "." of it) of ("192.168.0.1"; "128.168.0.1"; "10.168.0.1"; "1.2.3.4")
A: c0a80001, -3232235521
A: 80a80001, -2158493697
A: aa80001, 178782209
A: 1020304, 16909060
T: 0.690 ms
I: plural ( string, integer )

So we would pass these integer values back as the return code. To convert them back into IP addresses, we would check whether the value is negative; if the integer is positive, just convert it back into hexadecimal, or bit sets, or IP addresses; if the integer is negative, again subtract it from zero (making it positive again) and convert that result to hexadecimal or IP address

q: ((if it > 0 then it else 0 - it) of it as hexadecimal) of (-3232235521; -2158493697; 178782209; 16909060)
A: c0a80001
A: 80a80001
A: aa80001
A: 1020304
T: 2.648 ms
I: plural string

The important things to check are that the hexadecimal values we return in the last query (converting positive/negative integers into hexadecimal) are the same hex values we calculated for the IP address. For the four IPs I tested they match.

2 Likes

Thats some pretty cool stuff…nice!

I was wondering if some of those parameters could be negated by treating the IP as a version then parsing the octets via the major/minor/patch/revision inspectors, eg

Q: (((major revision of it * 256 * 256 * 256) + (minor revision of it * 256 * 256) + (patch revision of it * 256) + (build revision of it)) of (“192.168.0.1” as version))
A: 3232235521
T: 0.053 ms
I: singular integer

3 Likes

Ooh nice trick! I’ve seen IP addresses treated as versions before, but I’m afraid I forgot all about that. A much simpler handling, bravo!

1 Like

Using your logic for breaking up the IP address my test case is much more readable - the " * 256" takes care of left-shifting too.

Q: (it, it as hexadecimal, it as bits, (if bit 31 of (it as bits) then 0 - it else it)) of ((major revision of it * 256 * 256 * 256) + (minor revision of it * 256 * 256) + (patch revision of it * 256) + (build revision of it)) of  (it as version) of ("192.168.0.1"; "128.168.0.1"; "10.168.0.1"; "1.2.3.4")
A: 3232235521, c0a80001, 11000000101010000000000000000001, -3232235521
A: 2158493697, 80a80001, 10000000101010000000000000000001, -2158493697
A: 178782209, aa80001, 1010101010000000000000000001, 178782209
A: 16909060, 1020304, 1000000100000001100000100, 16909060
T: 0.421 ms
I: plural ( integer, string, bit set, integer )
2 Likes

I like the version trick and the leftshift tricks above.
I have usually used bits to strings and concatenation in this arena

q: bit set (concatenation of (lasts 8 of ("00000000" & it) of (substrings separated by "." of it as integer as bits as string)) of "192.168.0.1") as integer 
A: 3232235521
T: 0.147 ms
I: singular integer

It just takes each 8 bit and concatenates it to get the 32 bit (mind the front padding)
(but I missed the negative rule of 2 thing… needs more work!)

q: (if bit 31 of it then 0-it as integer else it as integer) of bit sets((concatenations of lasts 8 of padded strings of (substrings separated by "." of it as integer as bits)) of ("192.168.0.1"; "128.168.0.1"; "10.168.0.1"; "1.2.3.4"))
A: -3232235521
A: -2158493697
A: 178782209
A: 16909060

And after validating vs an on-line calculator, I can see we have it wrong with the 0 - it. it should be it - maxInt or it-4294967296

example showing a match with Wikipedia’s 4 bit table

q: (item 0 of it, item 1 of it, (if item 1 of it starts with "0" then item 0 of it else ((item 0 of it-16)))) of (it, lasts 4 of padded strings of (it as bits)) of (integers in (0,15))
A: 0, 0000, 0
A: 1, 0001, 1
A: 2, 0010, 2
A: 3, 0011, 3
A: 4, 0100, 4
A: 5, 0101, 5
A: 6, 0110, 6
A: 7, 0111, 7
A: 8, 1000, -8
A: 9, 1001, -7
A: 10, 1010, -6
A: 11, 1011, -5
A: 12, 1100, -4
A: 13, 1101, -3
A: 14, 1110, -2
A: 15, 1111, -1

So now that we have the rule worked into relevance, let’s try this one

q: ((if it starts with "0" then (bit set (it) as integer) else (bit set (it) as integer - 4294967296)) of( concatenations of (lasts 8 of padded strings of (substrings separated by "." of it as integer as bits)))) of ("192.168.0.1")
A: -1062731775

or with the other example from actionscript, with the cool bit flipping trick.

q: ((if it starts with "0" then (bit set (it) as integer) else (bit set (it) as integer - 4294967296)) of( concatenations of (lasts 8 of padded strings of (substrings separated by "." of it as integer as bits)))) of ("192.168.1.1")
A: -1062731519
3 Likes

Or in action

download now as ip.txt http://api.ipify.org

parameter "address"="{line 1 of file "__Download/ip.txt"}"

parameter "IPasSignedInt"="{((if it starts with "0" then (bit set (it) as integer) else (bit set (it) as integer - 4294967296)) of( concatenations of (lasts 8 of padded strings of (substrings separated by "." of it as integer as bits)))) of parameter "address"}"

exit {parameter "IPasSignedInt"}

Which is a REALLY FRIGGIN INSPIRED way to get the public IP address in the console without an analysis property.

Hat’s off for creativity @eg2428

2 Likes

Another option would be to get the public IP and save it in a client setting, which would cause it to be reported without creating an analysis.

I have quite a few examples of getting a devices public IP on bigfix.me but my favorite is using nslookup to query a fake DNS name that always responds with the requesters public IP.

3 Likes

Thanks everyone for all the awesome insight and explanations — I didn’t expect to learn so much from what I thought was a pretty silly question. This has been great!

1 Like