Bitwise Operations and Common Uses: Decoding TCP and IPv4 Headers using AND and Bitwise Shifts
Wrapping your head around the use of bitwise operators can be daunting in the beginning. In my next few posts, I’m going to attempt to walk through some common use cases for bitwise operators that I’ve encountered.
The first use case I’m going to discuss is protocol decoding. Data is stored in bytes, each of which is comprised of eight bits, with a total of 256 possible values per byte (0 to 255). Many common protocols, especially long-lived ones like IPv4, pack multiple values into a single byte to save space. Here’s an example.
Several of the values here do not line up directly on octet (byte) boundaries. From this chart, how would we get the version, which takes up only the first half of the first byte? To begin, we need to get the whole byte. A common value here would be 69
in decimal representation. Let’s take a look at what that actually looks like in binary.
Bitwise Right-Shift
A bitwise right-shift simply moves all the bits in a value one position to the right, dropping the rightmost bit and filling the leftmost bit with zero each time the values are moved.
0 1 0 0 0 1 0 1 = 69
To obtain our value, we will shift those top four bits to the bottom four bits of the byte by doing a right shift (>>
). It will fill in the previous four bits with zeroes.
69 >> 4 = 4
Let’s take a look at the bit pattern.
0 0 0 0 0 1 0 0 = 4
This is an IPv4 header, so 4
is the correct value.
Bitwise AND
AND
takes two operands and returns a value with only the bits set to 1 that were set to 1 in both operands. If either operand has a zero for a bit in a position, it will be zero in that position in the result.
Let’s try to get the IHL number. We start back with 69
.
0 1 0 0 0 1 0 1 = 69
We then mask the bits we care about.
0 0 0 0 1 1 1 1 = 15
This is equal to the decimal number 15
. Next, we do our AND operation.
15 & 69 = 5
Since the bits were already in the correct position, we didn’t need to perform any shifts. The default value of the IHL field in an IPv4 header is 5
, so we’ve got the correct answer! Let’s look at the bit pattern.
0 0 0 0 0 1 0 1 = 5
AND with Flags
It’s common to see single bits in a byte used as flags. TCP is a good example.
In the 13th octet, you will see that all eight bits are used for individual flags. Let’s say that the SYN
and ACK
flags are set.
0 0 0 1 0 0 1 0 = 18
The easiest way to deal with this is an AND
. Let’s take a look at the SYN
and ACK
flags. The ACK
flag is in the second position. Let’s mask it off.
0 0 0 0 0 0 1 0 = 2
To check for the individual flag, we simply use an AND
, and then confirm that the result is equal to the value of the mask.
18 & 2 = 2
We can also use this technique to check for multiple bits at once. Let’s make a mask to check for three bits being set.
0 0 0 1 0 0 1 1 = 19
18 & 19 = 18
Since the result was 18
but our mask was 19
, not all three flags were set.
Combining Right-Shift with AND
Sometimes you’ll want to get a multi-bit value that’s in the middle of a byte. We can combine these two operators to get the data we want, and what’s more, we can do them in any order. Let’s take a look at octet 12 of the TCP header. What if we want to take a look and see if there’s data in the reserved
section? We’ll need to mask those bits and then shift them to the right (or shift them to the right and mask them second). Here’s some random data.
0 1 0 1 0 0 1 0 = 82
Let’s do a shift first and see what happens.
82 >> 1 = 41
And here’s the bit pattern
0 0 1 0 1 0 0 1 = 41
Now we’ll mask the last three bits
0 0 0 0 0 1 1 1 = 7
And we’ll do an AND
41 & 7 = 1
Which has the bit pattern of
0 0 0 0 0 0 1 = 1
Let’s try in the other order.
We start with our original value.
0 1 0 1 0 0 1 0 = 82
Now we mask the three bits we want
0 0 0 0 1 1 1 0 = 14
We perform our AND
82 & 14 = 2
Which has a bit pattern of
0 0 0 0 0 0 1 0 = 2
Which we then shift right one place
2 >> 1 = 1
Which gives us the same bit pattern of
0 0 0 0 0 0 1 = 1