Grokking SYSCTL and the Art of Smashing Kernel Variables

During the course of hacking at a kernel, one may want to read or modify a variable, or struct, inside the kernel as a means of debugging their work, gathering data for analysis, or simply tweaking system behavior (such as raising the maximum socket buffer space allowed). The FreeBSD designers were nice enough to provide a means to peak into the internal of the kernel, and perhaps write that value if necessary. Welcome to the world of sysctl.

Sysctl variables are laid out in a hierarchical fashion. As an example let's decompose net.inet.tcp.sendspace 'net' is the root of the network sysctl variables, 'net.inet' is the root of the inter-networking sysctl variables, 'net.inet.tcp' is the root of sysctl variables pertaining to TCP and 'net.inet.tcp.sendspace' is a child node that defines the amount of send buffer space available to a TCP connection.

First, command line stuff:

   # look at the value
   % sysctl net.inet.tcp.sendspace
   net.inet.tcp.sendspace: 32768

   # get a description of the value with the -d flag
   % sysctl -d kern.ipc.maxsockbuf
   kern.ipc.maxsockbuf: Maximum socket buffer size

   # change the value 
   # (the linux man page for sysctl recommends using -w flag
   #    when writing variables but no explanation) 
   % sysctl net.inet.tcp.sendspace=1048576
   32768 -> 1048576

   # search for a value (in the net.inet.tcp tree)
   % sysctl -a | grep net.inet.tcp
   ... list of values ...
    

Now, let's say we want to write a program to actually read these values in a C program. One may want to do this to read a struct, whose values cannot be easily read using the command line tool, or to generate a data file for later analysis. Start with "man -S3 sysctl" on FreeBSD.

  // which should give you something like this...
  #include <sys/types.h>
  #include <sys/sysctl.h>

  int sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, 
    void *newp, size_t newlen);

  int sysctlbyname(const char *name, void *oldp, size_t *oldlenp, 
    void *newp, size_t newlen);
    
That's great, perhaps usage would be more helpful, keep in mind this works for simple data types (int, long, float) as well as structs
 
  // let's say you actually wanted some value, say net.inet.tcp.sendspace
  int sendspace, len;
  len = sizeof(int);
  sysctlbyname("net.inet.tcp.sendspace", &sendspace, &len, NULL, 0);

  // or for a struct
  struct tcpstat tcpstat;
  int stat_len;
  stat_len = sizeof(stat_len);
  sysctlbyname("net.inet.tcp.stats", &tcpstat, &stat_len, NULL, 0);
    
Last but not least, let's say you actually wanted to add your own sysctl variables into a kernel you might be compiling (you dashing hero you). For example sake, let's say you are implementing alternative congestion algorithms for TCP and wanted to switch between them without reloading a kernel. You might want to add the sysctl variable net.inet.tcp.congestion and set it to change the congestion algorithm used.
  u_int tcp_custom_congestion = 2;
  SYSCTL_UINT(_net_inet_tcp_, OID_AUTO, congestion, CTLFLAG_RW, &tcp_custom_congestion, 
    0, "which custom TCP congestion algorithm to use at run-time");
    
Which would then give you the sysctl variable
net.inet.tcp.congestion
for your usage initialized to 2. You need to shove these lines into some kernel code, probably where you'll remember, and in the scope at the point of usage, recompile, reboot, then use the command line sysctl tool to verify it was added.
Author: Andy Jones - (April 3rd, 2006)