How To: View or Set Socket Receive Buffer Size

A Linux system has a default socket buffer size for receiving and sending data. These values can be modified on most Linux systems, provided your process is running as root. Modifying these values can help increase network processing performance on both send and receive of packets.

Linux has a set of default values that are viewable via the /proc filesystem:

/proc/sys/net/core/
 
/rmem_default: The default setting of the socket receive buffer in bytes.
 
/rmem_max: The maximum receive socket buffer size in bytes.
 
/wmem_default: The default setting (in bytes) of the socket send buffer.
 
/wmem_max: The maximum send socket buffer size in bytes.

If you output the rmem_max you can view the size in bytes:

$ cat /proc/sys/net/core/rmem_max
131071

These values can be modified by echoing a value to these variables. If you plan on modifying the socket receive buffer size via a C command then the largest value you can set will be less than or equal to what is specified in the rmem_max variable. So if you require a very large value, you must modify the kernel maximum value rmem_max, then modify the value of your socket.

Read Current Socket Receive Buffer Size

To read your current socket receive buffer size is quite easy in C, its just a matter of using the correct function with the correct set of arguments. The function used to get socket information is known as getsockopt, which takes the arguments:

int getsockopt(int sockfd, int level, int optname, void *optval,
 socklen_t *optlen);

The first argument is your socket, the second argument is the API level, optname is the socket option (SO_RCVBUF), optval is value read back from the socket stating the receive buffer size, and optlen is the is the size of the buffer point to by optval. So lets put this into practice:

unsigned int m;
int n;
 
m = sizeof(n);
getsockopt(socket, SOL_SOCKET, SO_RCVBUF, (void *)&n, &m);
fprintf(stdout, "The socket receive buffer size is: %u\n", n);

The integer n contains the returned buffer size. You will notice the option name used SO_RCVBUF. This is outlined in the socket manual page, among other read/set values available. The manual page output for SO_RCVBUF is:

SO_RCVBUF
Sets or gets the maximum socket receive buffer in bytes. The kernel doubles this value (to allow space for bookkeeping overhead) when it is set using setsockopt(2), and this doubled value is returned by getsockopt(2). The default value is set by the /proc/sys/net/core/rmem_default file, and the maximum allowed value is set by the /proc/sys/net/core/rmem_max file.

Now that we can read the value, lets increase the maximum size and set a new value.

Set Socket Receive Buffer Size

To increase the socket buffer size for receives we utilize the setsockopt function.

echo 2097152 > /proc/sys/net/core/rmem_max

Then in C we can now set our socket to a value half that size…lets not break the bank.

int setsockopt(int sockfd, int level, int optname, cont void *optval,
 socklen_t optlen);

The arguments for the function are nearly identical. The optval is now a const void, and the socklen_t now takes the actual variable rather than address of. To set a size:

int buff_size;
 
// In bytes
buff_size = 1048576;
 
setsockopt(socket, SOL_SOCKET, SO_RCVBUF, &buff_size, (int)sizeof(buff_size));

As mentioned earlier this function will set the value to less than or equal to the maximum size set within the kernel variable (rmem_max). There are also other values that can be set or modified in kernel. Have fun tweaking these values to get better performance.

Set File Permissions With chmod In Linux

If you have ever needed to protect your files, or set a directory to read only in Linux, you have probably used chmod. If you didn’t, then you probably wished that you had heard of chmod. Chmod stands for change mode and allows the user to modify access permissions to specific files.

To display a file or directories current permissions you can use the ls command. For example:

$ ls -l
-rw-r--r--  1 erik erik       1014 2010-10-28 13:30 taps.sh

The first column of -rw-r–r– is actually showing you the current file permission for taps.sh . The output is actually broken into four parts.

-| rw- | r– | r– . The first column is used to identify:

  • – denotes a regular file
  • d denotes a directory
  • b denotes a block special file
  • c denotes a character special file
  • l denotes a symbolic link
  • p denotes a named pipe
  • s denotes a domain socket

The second column outlines what the current users permissions are, the third column outlines what group members are allowed to do, and the forth column outlines what other users may do. The following table outlines in detail the column information. The chmod functions as:

$ chmod [Reference][Operator][Mode] file1 file2 etc.

ReferenceClassDescription
uuserthe current file owner
ggroupthe file group members
oothersgeneric users who are not part of the group or owner of the file
aallugo is equivalent to the ‘a’

The chmod command will use the following operations to modify the user, group, and other fields. For example:

OperatorDescription
+the addition sign tells chmod to add the value
the dash will remove the modes from the class we specify
=this sets the exact mode for the file

The modes indicate which permissions are to be set for read, write, execute. For example:

ModeNameDescription
rreadsets the read permission
wwriteuse to write to a file or directory
xexecuteuse to set the execute for a file or directory

Here are some examples using the symbolic notation of changing the file permissions.

Add Read and Write Permissions to User and Group classes

For example:

$ ls -l taps.sh
-rw-r--r-- 1 erik erik 1014 2010-10-28 13:30 taps.sh
$ chmod ug+rw taps.sh
$ ls -l taps.sh
-rw-rw-r-- 1 erik erik 1014 2010-10-28 13:30 taps.sh

Notice in this example, only the group class (third column) changed because I already had write permissions as the current user.

Add Read, Write, Execute to User and Group

Lets say we want taps.sh to have all permissions to those who are lucky enough to be in our group, or be us. To do so:

$ chmod +rwx taps.sh
$ ls -l taps.sh
-rwxrwxr-x 1 erik erik 1014 2010-10-28 13:30 taps.sh

Using the chmod +rwx command, we ‘add’ the read, write, and execute option to our file.

Well that is all great, but for me I usually want to other make a file read only for myself, or executable for anyone. This can be done using numerics rather than using these symbolic notations mentioned above.

Numeric Example For Read-Only

The numeric’s group values together, for example:

#Permission
7full
6read and write
5read and execute
4read only
3write and execute
2write only
1execute only
0none

I use chmod 644 filename quite often. This sets the permissions to read only for group and other, i.e. only the user may read or write to the file.

$ ls -l taps.sh
-rwxr-xr-x 1 erik erik 1014 2010-10-28 13:30 taps.sh
$ chmod 644 taps.sh 
$ ls -l taps.sh
-rw-r--r-- 1 erik erik 1014 2010-10-28 13:30 taps.sh

Numeric Example For Execute

Another favourite of mine is execute by anyone. This comes in the form of 755.

$ ls -l taps.sh
-rw-r--r-- 1 erik erik 1014 2010-10-28 13:30 taps.sh
$ chmod 755 taps.sh 
$ ls -l taps.sh
-rwxr-xr-x 1 erik erik 1014 2010-10-28 13:30 taps.sh

So the 755, referring to the table means. User = 7 = all, Group = 5 = read and execute, Other = 5 = read and execute, as shown in the ls output above. This way you can mix and match the numerics, if you want it open to everyone you can use 777.

Change Permissions Recursively With ‘chmod -R’

If you want to set the permissions for all files under a directory to the same setting, you can pass the -R command in front of the permission you would like to use.

$ chmod -R 777 /home/erik/

This will set every file under my home directory to ‘all’, i.e. read, write, execute by anyone.

Hopefully this makes sense to you. Rather than giving you a huge set of examples, I hope I have taught you how to fish!

Linux Kernel: Unlikely / Likely Macros

At one point I was debugging some issue in the linux kernel and I came across an odd “if” statement. It was something along the lines of:

if(likely(buf)){
  ... do something fancy...
}

First I thought to myself…if likely what? Its likely that car runs on gas? I really had no idea what it meant.  It turns out the likely and unlikely macros actually inform the C compiler that a block of code will “likely” be called more often than say the else case, essentially branch prediction.  This little hint of information will change how the compiler boils the code down to assembly and can give your code a performance increase.  The likely and unlikely are defined in the “compiler.h” of the linux kernel.

#define likely(x)        __builtin_expect(!!(x), 1)
#define unlikely(x)     __builtin_expect(!!(x), 0)

So if the case of likely is called, you expect !!x (not not of x) or x to be true (equal to 1).

As of GCC version >= 2.96 that __builtin_expect feature allows us developers the ability to tell the compiler branch prediction information that it in turn can use to create more efficient assembly code. According to the GCC manual page:

if (__builtin_expect (x, 0))
                foo ();
 
     [This] would indicate that we do not expect to call `foo', since we
     expect `x' to be zero.

Behind the scenes GCC will setup the assembly code to execute sequentially for the more ‘likely’ case, and save the jump for the more ‘unlikely’ case. Since the jump assembly commands are more costly and time consuming it makes sense to help inform the compiler of how your code should be run. If you know where your bottleneck is in code, this optimization can be helpful. This is especially why it is done in Kernel code!

C ‘offsetof’ Macro Explanation And Example

If you have ever had to deal with pointer alignment issues then the C offsetof macro is your friend. It is often quite difficult to identify alignment issues other than seeing odd behaviour in your program. It is assumed the compiler will pad structures accordingly based on the architecture you are compiling your code for. Unfortunately, this is not always the case, some architectures need all structures to be 32-bit aligned. This can be mitigated by using some C macros like __packed__ or __padded__, or even using some gcc flags. You can however, use some of the predefined macros in your program to help identify these odd behaviours, one of which is the offsetof Macro.

The offsetof macro will return the offset of the field member from the start of the structure type. It is defined as:

// Header required
#include <stddef.h>
 
// Function definition
size_t offsetof(type, member);

This is useful because the size of the fields that make the structure can differ across implementations. Some compilers will automatically pad bytes between fields to align them correctly for that CPU, these means that just because you have 1 char followed by 1 int, the int may not be 1 byte after the start of the first char.

Knowing how the bytes are laid out in memory can help identify where pointer alignment issues may occur, especially if you are pointing to structures and modifying data in memory.

An example of using the offsetof macro:

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
 
struct test {
        char a;
        int b;
        double c;
        long d;
};
 
int main()
{
        struct test data;
 
        fprintf(stdout, "char: %u, int: %u, double: %u, long: %u\n", 
                offsetof(struct test, a), 
                offsetof(struct test, b), 
                offsetof(struct test, c), 
                offsetof(struct test, d));
 
        return 0;
}

Running the program:

$ ./offset 
char: 0, int: 4, double: 8, long: 16

All calculations are based off the start of the structure, and thus our char is at boundary 0, the integer begins 4 bytes after. Wait?! I thought a char was only 1 byte? Well guess what, it is, the compiler has padded the structure to have the integer begin on a 32bit boundary to help alleviate pointer alignment issues. This is what we want, and from our perspective it really makes no difference as long as when we reference the integer it references 4 bytes after char instead of simply 1 byte after char.

The offsetof macro is defined using a special form, lets take a look:

#define offsetof(st, m) __builtin_offsetof(st, m)

Behind the scenes this function does some pointer arithmetic to identify the offset of each member of the structure then returns that offset to the programmer. Nifty function! Using the offsetof function and displaying pointer addresses you can help narrow down oddities in your code that may be due to pointer alignment and byte boundary issues.

Byte Swap Little Endian and Big Endian

I have run into the Endian (not Indian) debate on numerous occasions. I even wrote an article explaining how to detect the Endianness of your operating system earlier. Simply detecting the endianness of your system is a good starting point in understand Endianess and what it means. In some cases you may need to flip your bytes from big Endian to little Endian, this is especially relevant when dealing with network packets and network programming in C. In network programming sometimes fields in a network packet will have fields that are in little Endian when most of your network fields are in big Endian, and to verify these fields you may need to swap Endianess.

For more information regarding Endianness head to the wiki article. There are also built-in C functions that do this including ntohs, ntohl, htons, htonl, however these functions are not always transferable to another architecture (MIPS, ARM) which also differ in Endianness. Therefore, I have written a few functions to byte swap 16-bit, 32-bit and 64-bit unsigned integers, if you are dealing with signed integers simply modify the functions to be signed.

u_int16_t swap_u16(u_int16_t i) {
    u_int8_t c1, c2;
 
    c1 = i & 255;
    c2 = (i >> 8) & 255;
 
    return (c1 << 8) + c2;
}
 
u_int32_t swap_u32(u_int32_t i) {
    u_int8_t c1, c2, c3, c4;    
 
    c1 = i & 255;
    c2 = (i >> 8) & 255;
    c3 = (i >> 16) & 255;
    c4 = (i >> 24) & 255;
 
    return ((u_int32_t)c1 << 24) + ((u_int32_t)c2 << 16) + ((u_int32_t)c3 << 8) + c4;
}
 
u_int64_t swap_u64(u_int64_t i) {
    u_int8_t c1, c2, c3, c4, c5, c6, c7, c8; 
 
    c1 = i & 255;
    c2 = (i >> 8) & 255;
    c3 = (i >> 16) & 255;
    c4 = (i >> 24) & 255;
    c5 = (i >> 32) & 255;
    c6 = (i >> 40) & 255;
    c7 = (i >> 48) & 255;
    c8 = (i >> 56) & 255;
 
    return ((u_int64_t)c1 << 56) + 
            ((u_int64_t)c2 << 48) + 
            ((u_int64_t)c3 << 40) + 
            ((u_int64_t)c4 << 32) + 
            ((u_int64_t)c5 << 24) + 
            ((u_int64_t)c6 << 16) + 
            ((u_int64_t)c7 << 8) + 
            c8;
}

As you can see these functions simply use a logical & with 0xFF (or 255 in decimal) then bit shift the value to its appropriate location depending on the size of the integer. If you apply the function again it will swap the values to their initial position.

Linux Bridge With ‘brctl’ Tutorial

Ever wanted to setup your desktop computer as a network bridge? A bridge differs from a router in that it only looks at layer 2 traffic (MAC addresses) whereas a router inspects at layer 3 of the OSI model (IP addresses). An interesting advantage of running a bridge on your Linux machine is that you can configure it as a transparent bridge with firewall filtering, you could even run something like SNORT, an intrusion detection system for monitoring traffic on the wire. But these are discussions for another day. I would like to cover the functionality of the brctl command in Linux.

The brctl command allows the user to interface with the kernel to actually configure the bridge. The brctl binary is from the bridge-utils package that can be found in the Debian or Ubuntu repositories. To utilize the brctl function you must be running as root or under sudo privileges. The set of commands that brctl provides is as follows:

# brctl 
Usage: brctl [commands]
commands:
        addbr           <bridge>                add bridge
        delbr           <bridge>                delete bridge
        addif           <bridge> <device>       add interface to bridge
        delif           <bridge> <device>       delete interface from bridge
        setageing       <bridge> <time>         set ageing time
        setbridgeprio   <bridge> <prio>         set bridge priority
        setfd           <bridge> <time>         set bridge forward delay
        sethello        <bridge> <time>         set hello time
        setmaxage       <bridge> <time>         set max message age
        setpathcost     <bridge> <port> <cost>  set path cost
        setportprio     <bridge> <port> <prio>  set port priority
        show                                    show a list of bridges
        showmacs        <bridge>                show a list of mac addrs
        showstp         <bridge>                show bridge stp info
        stp             <bridge> {on|off}       turn stp on/off

Display All Bridge Interfaces

To see all current bridge interfaces, execute the command:

# brctl show
bridge name     bridge id               STP enabled     interfaces

As you can see, I currently have no bridge interfaces noted by just column output. So lets add a bridge interface.

Create A Bridge

To create a bridge interface simply run the command:

# brctl addbr br0

Most people create their initial bridge as ‘br0′, you will see that on most OpenWRT or DD-WRT routers. Now if we output our interfaces using ifconfig we can see our interface. I will also bring the interface up.

# ifconfig br0 up
# ifconfig br0
br0       Link encap:Ethernet  HWaddr 26:bc:c7:e4:68:20  
          inet6 addr: fe80::24bc:c7ff:fee4:6820/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 B)  TX bytes:168 (168.0 B)

Our bridge is just like any other interface, it can even have an IP address assigned to it if you wanted (using ifconfig). Lets display our current bridges now:

# brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.000000000000       no

You will notice that we do not have STP enabled. STP is the spanning tree protocol that is used to avoid bridging loops. We can enable STP using a brctl command I will outline later. As you can also see here, our bridge has no interfaces in it. Lets add an interface.

Add Interfaces To A Bridge

To add an interface to your bridge is simple:

# brctl addif br0 eth0
# brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.001a921ddc9b       no              eth0

Notice how the bridge id changed once I added the interface. The bridge will also take on the MAC address of the first interface added to your bridge.

To make it a true bridge, we should probably have two interfaces within that bridge, executing the same command:

# brctl addif br0 eth1
# brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.001a921ddc9b       no              eth0
                                                        eth1

To remove interfaces from the bridge we utilize the delif flag of the brctl command.

Remove Interface From A Bridge

To remove eth1 from our bridge we can enter the command:

# brctl delif br0 eth1
# brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.001a921ddc9b       no              eth0

Those are the basic features of creating, adding, removing a bridge and its interfaces. There are a few more commands I would like to outline, including STP.

Turning STP On For Your Bridge

To configure your bridge to participate in a spanning tree, you can enter the command:

# brctl stp br0 on
# brctl show
bridge name     bridge id               STP enabled     interfaces
br0             8000.001a921ddc9b       yes              eth0

Display Learned MAC Address On Your Bridge

For the bridge to properly send traffic out the correct interface it keeps a table of all MAC addresses that it has seen and the interface it arrived on. To display this enter the command:

# brctl showmacs br0
port no mac addr                is local?       ageing timer
  2     00:01:29:d4:bd:59       no               139.95
  1     00:0c:29:2b:3e:77       no                19.50
  1     00:ab:76:ba:d0:22       yes                0.00
  2     00:ce:d6:aa:de:fa       yes                0.00

Notice there is an ageing timer. This is the amount of time (in seconds) since this mac address has been seen on the bridge. A ‘garbage’ collector will check every interval if the age is passed the acceptable limit and remove it from the table.

From the brctl manual page:

brctl setageingtime <brname> <time> sets the ethernet (MAC) address ageing time, in seconds. After seconds of not having seen a frame coming from a certain address, the bridge will time out (delete) that address from the Forwarding DataBase (fdb).

brctl setgcint <brname> <time> sets the garbage collection interval for the bridge to seconds. This means that the bridge will check the forwarding database for timed out entries every seconds.

The areas I have covered include the most used features of the brctl command. There are however, other features as shown by my first output of the brctl command. Refer to the manual page for more information (man brctl).

5 Useful Unix DD Command Examples

dd is a common Unix program whose primary purpose is the low-level copying and conversion of raw data. You can backup whole hard drives, create a large file filled with only zeros, create and modify image files at specific points, and even do conversions to upper case.

To display dd‘s help simply enter:

erik@debian:~$dd –help

Alright, lets get to the juicy stuff.

1. Make an ISO of a your favourite CD just for backing up purposes with dd:

dd if=/dev/cdrom of=/home/erik/myCD.iso bs=2048 conv=sync

Breaking down the commands:

  • if is “input file”, so in this case our cdrom drive at /dev/cdrom
  • of is “output file”, in this case myCD.iso
  • bs is “block size”, in this case 2048 bytes per block
  • conv is for conversion, in this case we are using “sync” which tells DD to execute synchronized input and output, this is needed for the CD-ROM as we want to read a whole block to ensure no data loss occurs.

2. Duplicate one hard disk partition to another hard disk with dd:

dd if=/dev/sda1 of=/dev/sdb1 bs=4096 conv=noerror

In this case everything is the same as example 1 but our conversion methods states that noerror should be executed, this tells DD to continue after read errors.

3. Fill a file with 1MB of random bytes with dd:

erik@debian:~$dd if=/dev/urandom bs=1024 count=1000 of=fun.bin 1000+0 records in 1000+0 records out 1024000 bytes (1.0 MB) copied, 0.198349 s, 5.2 MB/s

This time I stated that our block size is 1024 bytes, and we are going to make 1000 of them sequentially. I also used the built-in kernel device urandom which provides random bytes.

4. Skip first 128K of input file then write remaining with dd:

dd if=/home/erik/fun.bin skip=128k bs=1 of=/home/erik/fun2.bin

The skip command tells DD to move passed the (in this case) 128k of data in fun.bin then write the rest to fun2.bin. This can be handy if you have a large file that needs to be written across more than one partition. For instance, if you had 3 partitions each 128k. You wouldn’t want to write the same 128k to each partition, you would want to write the first 128k to partition 1, then from 128k-256k of the file to partition 2 and so on.

5. Using dd to convert a file to uppercase:

dd if=erik.txt of=erik_up.txt conv=ucase

Finally, we use conv again to do a conversion. In this case we convert with the specifier of ucase.

What is your favourite use of dd?

Bitwise Operations In C

Bitwise operations in C are used to implement masking of variables, produce logical AND’s, logical OR’s, XOR operations on two variables, the occasional NOT, and even some bit shifting. The operations are called bitwise operations because each operation is done bit by bit for each variable.

Here are the most common and supported bitwise operations that C provides.

Bitwise AND Operation In C

The AND operation, also known as a conjunction is true if and only if both values are true. The operator is a single & ampersand sign in C. So for example:

a & b

To produce a compound assignment where ‘a’ receives the value of “a & b” we can do:

a &= b

An example of how the bitwise operation takes place:

1 2 30101 (5) AND 0011 (3) = 0001 (1)

Working from right to left we produce each compare vertically, as if we were adding two large numbers. So, 1 & 1 = 1, 0 & 1 = 0, 1 & 0 = 0, 0 & 0 = 0. Thus 0001.

Bitwise OR Operation in C

The OR operation, aka the disjunction is true if at least one of its operands is true. In C its symbol is a single bar | or affectionately known as a pipe in Linux/Unix circles.

a | b

To produce a compound assignment where ‘a’ receives the value of “a | b” we can do:

a |= b

An example of how the OR operation takes place:

1 2 30111 (7) OR 0101 (5) = 0111 (7)

Execution occurs in the same manner mentioned under the AND operation.

Bitwise NOT Operation In C

The bitwise not symbol in C is the tilde, ~ symbol.

a = ~b

The not just inverses each bit within your variable:

1 2NOT 0011 (2) = 1100 (12)

So in this case, 2 becomes twelve as we flip the bits.

Bitwise XOR Operation In C

An exclusive disjunction or XOR is true if one but not both of its operands is true. This is sometimes used for basic encryption.

To produce an XOR in C we use the ^ symbol, also known as a carat.

a ^ b

To produce a compound assignment where ‘a’ receives the value of “a ^ b” we can do:

a ^= b

For example:

1 2 30101 (5) XOR 0011 (3) = 0110 (6)

So following the same methodology above, we XOR 5 and 3 and receive 6.

Bit-shifting Operations In C

The bit shifting that occurs in C is known as a logical shift because the bits that are shifted out are lost, rather than circulating to the front of that variable. To execute a shift in C, we use << or >> for left and right shift respectively.

The programmer may specify how many bits to shift by providing a number, for example:

a = b << 2;

This example will shift ‘b’ two bits to left, and assign that value to ‘a’. To shift to the right instead, use the >> symbols.

What will happen is, if our variable contained 2 in it:

1 2 30010 (2) // We execute the shift two places and notice 1 moved left two spots 1000 (8)

Bitwise operations on older systems were faster for addition and subtraction operations and largely faster than multiplication and division, however, on today’s architectures bitwise operations are essentially as efficient, with multiplication and division still requiring more operations.

Creating a Pipe in C

What is a pipe in C?

A pipe in C is a unidirectional data channel that can be used for interprocess communication. One process may write to the pipe, while another process may read from it. This was you can communicate from one process to another through this channel.

How do I use a pipe in C?

Here is a quick example from the pipe linux man page of setting up a pipe in C, with a parent process writing to the pipe and a child process reading from it.

#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
 
int
main(int argc, char *argv[])
{
	int pipefd[2];
	pid_t cpid;
	char buf;
 
	if (argc != 2) {
		fprintf(stderr, "Usage: %s \n", argv[0]);
		exit(EXIT_FAILURE);
	}
 
	if (pipe(pipefd) == -1) {
		perror("pipe");
		exit(EXIT_FAILURE);
	}
 
	cpid = fork();
	if (cpid == -1) {
		perror("fork");
		exit(EXIT_FAILURE);
	}
 
	if (cpid == 0) {    /* Child reads from pipe */
		close(pipefd[1]);          /* Close unused write end */
 
		while (read(pipefd[0], &amp;buf, 1) &gt; 0)
			write(STDOUT_FILENO, &amp;buf, 1);
 
		write(STDOUT_FILENO, "\n", 1);
		close(pipefd[0]);
		_exit(EXIT_SUCCESS);
 
	} else {            /* Parent writes argv[1] to pipe */
		close(pipefd[0]);          /* Close unused read end */
		write(pipefd[1], argv[1], strlen(argv[1]));
		close(pipefd[1]);          /* Reader will see EOF */
		wait(NULL);                /* Wait for child */
		exit(EXIT_SUCCESS);
	}
}

PLC5 – CSPv4 Wireshark Dissector

A long time ago I had been looking at PCAP traces of a PLC5 communicating with RsLinx. Wireshark just saw it as a blob of data on top of the TCP header. Well this just would not do. Wireshark provides a nice interface for using LUA to write your own dissector. This is what I ended up doing for the CSPv4 data (which is actually CSPv4 Header + LSAP + PCCC or PC cubed). An added bonus of writing a dissector for an unknown protocol is that the protocol filter will also register the bytes you define, so you can easily filter a packet stream with your newly defined byte fields.

A big thanks to these two articles from Lynn’s Iatips, specifically:

As well as the Rockwell document that provided valuable PCCC format information (Chapter 6,7):

Wireshark Dissector for PLC5 – CSPv4 + LSAP + PCCC

Without further ado – the LUA code for the Wireshark dissector. Following this code include is a screenshot and instructions of how to include this parser within Wireshark.

The code:

-- CSPv4 Parser --------------------------------
-- 
-- Date: July 25, 2012
-- Author: Erik Schweigert
-- E-mail: erik@linuxtips.ca
--
-- Purpose: To decode the CSPv4 Packet
-- 		CSPv4 + LSAP + PCCC
------------------------------------------------
p_cspv4 = Proto("cspv4","CSPv4")
p_lsap = Proto("lsap","LSAP")
p_pccc = Proto("pccc","PCCC")
 
-- ----------------- CSPv4 Header ------------
local f_mode = ProtoField.uint8("cspv4.mode", "Mode", base.HEX)
local f_submode = ProtoField.uint8("cspv4.submode", "Submode", base.HEX)
local f_data_length = ProtoField.uint16("cspv4.data_length", "Data Length", base.HEX)
local f_conn_id = ProtoField.uint32("cspv4.conn_id", "Connection ID [slave/server]", base.HEX)
local f_status = ProtoField.uint32("cspv4.status", "Status", base.HEX)
local f_context = ProtoField.bytes("cspv4.context", "Context", base.HEX)
-- ---------------- End CSPv4 Header -----------
 
-- ------------------ LSAP ---------------------
-- Local 
local f_dest = ProtoField.uint8("cspv4.dst", "Destination Byte", base.HEX)
local f_res5 = ProtoField.uint8("cspv4.res5", "Control Byte", base.HEX)
local f_src = ProtoField.uint8("cspv4.src", "Source Byte [Master Address]", base.HEX)
local f_lsap = ProtoField.uint8("cspv4.lsap", "LSAP", base.HEX)
 
-- Remote
local f_resX = ProtoField.uint8("cspv4.resX", "Mystery Byte", base.HEX)
local f_dst_link = ProtoField.uint16("cspv4.dst_link","Destination Link Address", base.HEX)
local f_dst_station = ProtoField.uint16("cspv4.dst_station", "Destination Station Address", base.HEX)
local f_resY = ProtoField.uint8("cspv4.resY", "Mystery Byte 2", base.HEX)
local f_src_link = ProtoField.uint16("cspv4.src_link", "Source Link Address", base.HEX)
local f_src_station = ProtoField.uint16("cspv4.src_station", "Source Station Address", base.HEX)
local f_resZ = ProtoField.uint8("cspv4.resZ", "Mystery Byte 3", base.HEX)
-- ------------------ End LSAP ------------------
 
-- ------------------ PCCC ----------------------
local f_pccc_command = ProtoField.uint8("cspv4.pccc_command", "Command Code", base.HEX)
local f_pccc_sts = ProtoField.uint8("cspv4.pccc_sts", "Status Code", base.HEX)
local f_pccc_tns = ProtoField.uint16("cspv4.pccc_tns", "Transaction Number", base.HEX)
local f_pccc_fnc = ProtoField.uint8("cspv4.pccc_fnc", "Function Code", base.HEX)
local f_pccc_addr = ProtoField.uint16("cspv4.pccc_addr", "Address of Memory Location", base.HEX)
local f_pccc_size = ProtoField.uint8("cspv4.pccc_size", "Size", base.HEX)
local f_pccc_data = ProtoField.bytes("cspv4.pccc_data", "Data", base.HEX)
-- ------------------ End PCCC -------------------
 
-- CSPv4 Fields
p_cspv4.fields = {f_mode}
p_cspv4.fields = {f_submode}
p_cspv4.fields = {f_data_length}
p_cspv4.fields = {f_conn_id}
p_cspv4.fields = {f_status}
p_cspv4.fields = {f_context}
p_cspv4.fields = {f_dest}
p_cspv4.fields = {f_res5}
p_cspv4.fields = {f_src}
p_cspv4.fields = {f_lsap}
 
-- Remote LSAP Fields
p_cspv4.fields = {f_resX}
p_cspv4.fields = {f_dst_link}
p_cspv4.fields = {f_dst_station} 
p_cspv4.fields = {f_resY}
p_cspv4.fields = {f_src_link}
p_cspv4.fields = {f_src_station}
p_cspv4.fields = {f_resZ}
 
-- PCCC Fields
p_cspv4.fields = {f_pccc_command}
p_cspv4.fields = {f_pccc_sts}
p_cspv4.fields = {f_pccc_tns}
p_cspv4.fields = {f_pccc_fnc}
p_cspv4.fields = {f_pccc_addr}
p_cspv4.fields = {f_pccc_size} 
p_cspv4.fields = {f_pccc_data}
 
function build_cspv4_header(buf)
	build_request(buf)
	build_submode(buf)
 
	subtree:add(f_data_length, buf(2,2))
	subtree:add(f_conn_id, buf(4,4))
	subtree:add(f_status, buf(8,4))
	subtree:add(f_context, buf(12,16))
end
 
function build_request(buf)
	if buf(0,1):uint() == 1 then
		subtree:add(f_mode, buf(0,1)):append_text(" (Request)")
	elseif buf(0,1):uint() == 2 then
		subtree:add(f_mode, buf(0,1)):append_text(" (Response)")
	else
		subtree:add(f_mode, buf(0,1))
	end
end
 
function build_submode(buf)
	if buf(1,1):uint() == 1 then 
		subtree:add(f_submode, buf(1,1)):append_text(" (Connection)")
	elseif buf(1,1):uint() == 7 then
		subtree:add(f_submode, buf(1,1)):append_text(" (PCCC)")
	else
		subtree:add(f_submode, buf(1,1))
	end  
end
 
function build_lsap(buf, root)
 
	lsap_tree = root:add(p_lsap, buf(28))
 
	lsap_tree:add(f_dest, buf(28,1))
	lsap_tree:add(f_res5, buf(29,1))
	lsap_tree:add(f_src, buf(30,1))
 
	if buf(31,1):uint() == 0 then
		lsap_tree:add(f_lsap, buf(31,1)):append_text(" (Local Form)")
	elseif buf(31,1):uint() == 1 then
		lsap_tree:add(f_lsap, buf(31,1)):append_text(" (Remote Form)")
		build_lsap_remote(buf, lsap_tree)
	else
		lsap_tree:add(f_lsap, buf(31,1))
	end
end
 
function build_lsap_remote(buf, lsap_tree)	
	lsap_tree:add(f_resX, buf(32,1))
	lsap_tree:add(f_dst_link, buf(33,2))
	lsap_tree:add(f_dst_station, buf(35,2))
	lsap_tree:add(f_resY, buf(37,1))
	lsap_tree:add(f_src_link, buf(38,2))
	lsap_tree:add(f_src_station, buf(40,2))
	lsap_tree:add(f_resZ, buf(42,1))
end
 
function build_pccc(buf, root)
 
	pccc_tree = root:add(p_pccc, buf(32))
 
	-- Ensure its PCCCC
	if buf(1,1):uint() ~= 7 then end
 
	if buf(31,1):uint() == 1 then
		offset = 11
	else
		offset = 0
	end	
 
	pccc_tree:add(f_pccc_command, buf(32 + offset, 1))
	pccc_tree:add(f_pccc_sts, buf(33 + offset, 1))
	pccc_tree:add(f_pccc_tns, buf(34 + offset, 2))
	pccc_tree:add(f_pccc_fnc, buf(36 + offset, 1))
	pccc_tree:add(f_pccc_addr, buf(37 + offset, 2))
	pccc_tree:add(f_pccc_size, buf(39 + offset, 1))	
	pccc_tree:add(f_pccc_data, buf(40 + offset, buf:len() - (40 + offset)))
end
 
-- cspv4 dissector function
function p_cspv4.dissector (buf, pkt, root)
	-- validate packet length is adequate, otherwise quit
	if buf:len() == 0 then return end
 
	pkt.cols.protocol = p_cspv4.name
 
	-- create subtree for cspv4
	subtree = root:add(p_cspv4, buf(0))
	-- add protocol fields to subtree
 
	build_cspv4_header(buf)
	build_lsap(buf, root)
	build_pccc(buf, root)
 
	-- description of payload
	subtree:set_text("CSPv4, CSPv4 Header Information")  
 
	-- add debug info if debug field is not nil
	if f_debug then
		-- write debug values
		subtree:add(f_debug, buf:len())
	end
end
 
-- Initialization routine
function p_cspv4.init()
end
 
-- register a chained dissector for port 2222
local tcp_dissector_table = DissectorTable.get("tcp.port")
dissector = tcp_dissector_table:get_dissector(2222)
  -- you can call dissector from function p_cspv4.dissector above
  -- so that the previous dissector gets called
tcp_dissector_table:add(2222, p_cspv4)

As you can see there is nothing ground breaking in this parser, and the code itself is quite rudimentary. A great enhancement would be to add the textual value of what the PCCC command vs function code actually equates to (read bit, write bit, etc).

Installing LUA Dissector to Wireshark

Now you have enhanced Wireshark to properly dissect your PLC5 packets – at least if they are CSPv4 with PCCC.

  • Save the lua script above to any folder and call the file cspv4.lua
  • Open init.lua in the Wireshark installation directory for editing. In Linux it can be found in /etc/wireshark/init.lua.  You will need Admin privileges on Windows Vista and 7.
  • Comment out the following line in init.lua (single line comments begin with --):
disable_lua = true; do return end;
  • Add the following lines to init.lua (at the very end):
dofile("/path/to/the/file/cspv4.lua")
  • Run Wireshark
  • Load a capture file that has the packets of your custom protocol or start a live capture.

Now you have enhanced Wireshark to properly dissect your PLC5 packets – at least if they are CSPv4 with PCCC.