I have two fields: Network_Address and Netmask. The Network_Address field has the network address of the network as field values and the Netmask field has the network mask as it's value. Here is an example:
Network_Address Netmask
10.1.1.0 255.255.255.0
How can I write a search so Splunk tells me the CIDR subnet rage for the two fields? I need the output to be put in a new field named CIDR.
This is what worked between @yuanliu and @ITWhisperer suggestions:
| eval bitmask = split(Netmask, ".")
| eval bitmask = 32 - sum(mvmap(bitmask, log(256 - bitmask,2)))
| eval CIDR = Network_Address . "/" . bitmask
FYI,
I added the following external command lookup to the App below.
Usage:
| makeresults
| eval Network_Address="10.1.1.0", Netmask="255.255.255.0"
| eval ip=Network_Address."/".Netmask
| lookup local=t ipcalclookup Address as ip OUTPUT Network Prefix
| eval CIDR=Network."/".Prefix
Numeral system macros for Splunk
https://splunkbase.splunk.com/app/6595
This is what worked between @yuanliu and @ITWhisperer suggestions:
| eval bitmask = split(Netmask, ".")
| eval bitmask = 32 - sum(mvmap(bitmask, log(256 - bitmask,2)))
| eval CIDR = Network_Address . "/" . bitmask
It's a tidy solution. Just keep in mind that it assumes Netmask is a valid network mask and Network_Address is a valid network address relative to Netmask. If either assumption is false, the resulting CIDR value is invalid.
To simplify netmask validation, I would introduce a lookup, e.g. netmask_lookup:
netmask,sigbits
0.0.0.0,0
128.0.0.0,1
192.0.0.0,2
224.0.0.0,3
240.0.0.0,4
248.0.0.0,5
252.0.0.0,6
254.0.0.0,7
255.0.0.0,8
255.128.0.0,9
255.192.0.0,10
255.224.0.0,11
255.240.0.0,12
255.248.0.0,13
255.252.0.0,14
255.254.0.0,15
255.255.0.0,16
255.255.128.0,17
255.255.192.0,18
255.255.224.0,19
255.255.240.0,20
255.255.248.0,21
255.255.252.0,22
255.255.254.0,23
255.255.255.0,24
255.255.255.128,25
255.255.255.192,26
255.255.255.224,27
255.255.255.240,28
255.255.255.248,29
255.255.255.252,30
255.255.255.254,31
255.255.255.255,32
| lookup netmask_lookup netmask as Netmask output sigbits
If sigbits (significant bits) is null, the netmask is invalid, and the network cannot be expressed in CIDR notation.
To mask the address using a valid netmask, let's introduce a macro to perform a bitwise AND between two 8-bit values [1]:
[bitand_8(2)]
args = x, y
definition = sum(1 * (floor($x$ / 1) % 2) * (floor($y$ / 1) % 2), 2 * (floor($x$ / 2) % 2) * (floor($y$ / 2) % 2), 4 * (floor($x$ / 4) % 2) * (floor($y$ / 4) % 2), 8 * (floor($x$ / 8 ) % 2) * (floor($y$ / 8 ) % 2), 16 * (floor($x$ / 16) % 2) * (floor($y$ / 16) % 2), 32 * (floor($x$ / 32) % 2) * (floor($y$ / 32) % 2), 64 * (floor($x$ / 64) % 2) * (floor($y$ / 64) % 2), 128 * (floor($x$ / 128) % 2) * (floor($y$ / 128) % 2))
iseval = 0
Combining all of the above:
| makeresults
| eval Network_Address="10.1.1.0", Netmask="255.255.255.0"
| lookup netmask_lookup netmask as Netmask output sigbits
| eval addr_octets=split(Network_Address, "."), netmask_octets=split(Netmask, ".")
| eval x=tonumber(mvindex(addr_octets, 0, 0)), y=tonumber(mvindex(netmask_octets, 0, 0)), addr0=`bitand_8(x, y)`
| eval x=tonumber(mvindex(addr_octets, 1, 1)), y=tonumber(mvindex(netmask_octets, 1, 1)), addr1=`bitand_8(x, y)`
| eval x=tonumber(mvindex(addr_octets, 2, 2)), y=tonumber(mvindex(netmask_octets, 2, 2)), addr2=`bitand_8(x, y)`
| eval x=tonumber(mvindex(addr_octets, 3, 3)), y=tonumber(mvindex(netmask_octets, 3, 3)), addr3=`bitand_8(x, y)`
| eval CIDR=addr0.".".addr1.".".addr2.".".addr3."/".sigbits
=> 10.1.1.0/24
We can validate with several cases:
| eval Network_Address="10.1.1.0", Netmask="255.255.254.0"
=> 10.1.0.0/23
| eval Network_Address="10.1.1.32", Netmask="0.0.0.0"
=> 0.0.0.0/0 (the default route)
| eval Network_Address="10.1.1.32", Netmask="255.255.255.255"
=> 10.1.1.32/32 (the host route)
| eval Network_Address="10.1.1.32", Netmask="255.0.255.0"
=> null (invalid netmask)
A simpler solution overall--and one compatible with both IPv4 and IPv6--would offload the work to either a custom command or an external lookup, but the above steps should work for any unprivileged Splunk user.
EDIT: I'm apparently stuck in a pre-9.0 world. You can replace the eval and bitand logic with a single ipmask() eval function [2]. That said, it isn't difficult to perform bitwise operations in core Splunk up to a certain size. For IPv6, you're still better off implementing a custom command or external lookup.
1. https://en.wikipedia.org/wiki/Bitwise_operation#Mathematical_equivalents
2. https://docs.splunk.com/Documentation/Splunk/9.0.4/SearchReference/ConversionFunctions#ipmask.28.26l...
Not so much a search, but mere calculation using the definition.
| eval bitmask = split(Netmask, ".")
| eval bitmask = sum(mvmap(bitmask, log(bitmask + 1, 2)))
| eval CIDR = Network_Address . "/" . bitmask
Your sample data will return
CIDR | Netmask | Network_Address | bitmask |
10.1.1.0/24 | 255.255.255.0 | 10.1.1.0 | 24 |
This seemed work for the most part. The only I issue I see is some of the values in the new CIDR field have .99435343685886 at the end.
Example:
Network address Netmask CIDR
10.21.72.1 255.255.254.0 10.21.72.0/23.99435343685886
Is there a way to trim the "." and the numbers to the right off it off?
@yuanliu is close but it fails for mask which aren't 255 - try flipping it to subtract the number of missing bits from 32
| eval bitmask = 32 - sum(mvmap(bitmask, log(256 - bitmask,2)))
Haha I didn't see this before I replied back to @yuanliu post. Your idea did the trick. Thank you both for the help!