5.8. The setsockopt() API call

As I explained in the previous topic, there is an API that can be used to set or change certain attributes about a socket, called 'setsockopt'. Here we will detail using that API call in RPG.

The IBM manual page for the setsockopt() API is found here: http://publib.boulder.ibm.com/pubs/html/as400/v4r5/ic2924/info/apis/ssocko.htm

The ever-helpful IBM manual refers to setsockopt() as "Set Socket Options" and proceeds to tell us "The setsockopt() function is used to set socket options". Gee, thanks.

The C language prototype for setsockopt() looks like this:

         int setsockopt(int socket_descriptor,
                        int level,
                        int option_name,
                        char *option_value,
                        int option_length);
     

By now, you're probably an old-pro at converting these prototypes to RPG. The C prototype tells us that the setsockopt() procedure receives 5 parameters. These parameters are an integer, an integer, another integer, a pointer, and yet another integer. It also returns an integer.

The 'char *option_value' parameter does require a bit more research. We know that it's a pointer to a character variable. But is it supposed to be a null-terminated string of characters? or is it supposed to be a single byte? or what? So, we page down to where it describes the option_value, we find that it says "A pointer to the option value. Integer flags/values are required by setsockopt() for all the socket options except SO_LONGER, IP_OPTIONS, . . ."

Oh great. So, it tells us it's a pointer to a character... but it really wants a pointer to an integer most of the time, and a pointer to other things at other times.

Fortunately, this doesn't present a big problem for us. In RPG, pointers can point to any data type. So really all we needed to learn from this is that we do not want to make it a pointer to a null terminated string.

Here's our RPG prototype. Hope it provides hours of enjoyment:

         D setsockopt      PR            10I 0 ExtProc('setsockopt')  
         D   socket_desc                 10I 0 Value                  
         D   level                       10I 0 Value                  
         D   option_name                 10I 0 Value                  
         D   option_value                  *   Value                  
         D   option_length               10I 0 Value                  
     

Please add that prototype to your SOCKET_H member.

Now, we'll need to define the constants for the 'level' and 'option_name' parameters, but first, a bit of explanation. TCP/IP is a "layered protocol". At the start of this tutorial, I explained a bit how this works, there's the Internet Protocol (IP) that handles the transportation of data across an inter-network. Running "on top of" that is (in this case) the Transmission Control Protocol (TCP), which uses the IP protocol to send and receive data, but adds the extra functionality of operating like a reliable-stream of data. On top of that is the Application-Level Protocol that varies from program to program. We send and receive this application data by calling the send() and recv() options on a socket.

So, when setting the options that control how a socket works, we may be setting them at different levels. Some options are set at the IP level, others at the TCP level, and others may pertain to the socket itself, so we have a special "socket level" to deal with.

For example, there is a "Time To Live" (TTL) value associated with every packet sent over the internet. This TTL defines the maximum number of routers or gateways the packet can travel through before being dropped.

This is useful because sometimes people make mistakes and misconfigure a router. If packets didnt have a maxmimum time to live, it would be possible for packets in error to bounce around the internet forever. Since this 'TTL' value relates to the routing of datagrams, it must be a part of the IP layer, therefore to change it with setsockopt, you set the 'level' parameter to the constant that means "IP layer" and set the option_name parameter to the constant that means "TTL".

Here are the values for the different "levels". Note that some of these are already defined in your SOCKET_H member, because they're used by the socket() API as well. You should add the other ones, though:

         D*                                                Internet Protocol     
         D IPPROTO_IP      C                   CONST(0)                          
         D*                                                Transmission Control  
         D*                                                Protocol              
         D IPPROTO_TCP     C                   CONST(6)                          
         D*                                                Unordered Datagram    
         D*                                                Protocol              
         D IPPROTO_UDP     C                   CONST(17)                         
         D*                                                Raw Packets           
         D IPPROTO_RAW     C                   CONST(255)                        
         D*                                                Internet Control      
         D*                                                Msg Protocol          
         D IPPROTO_ICMP    C                   CONST(1)                          
         D*                                                socket layer          
         D SOL_SOCKET      C                   CONST(-1)                         
     

Here are the values for the "option_name" parameter that are used at the "IP level":

         D*                                                  ip options         
         D IP_OPTIONS      C                   CONST(5)                         
         D*                                                  type of service    
         D IP_TOS          C                   CONST(10)                        
         D*                                                  time to live       
         D IP_TTL          C                   CONST(15)                        
         D*                                                  recv lcl ifc addr  
         D IP_RECVLCLIFADDR...                                                  
         D                 C                   CONST(99)                        
     

Here are the values for the "option_name" parameter that are used at the "TCP level":

         D*                                          max segment size (MSS)      
         D TCP_MAXSEG      C                   5                                 
         D*                                          dont delay small packets    
         D TCP_NODELAY     C                   10                                
     

Here are the values for the "option_name" parameter that are used at the "Socket Level":

         D*                                          allow broadcast msgs   
         D SO_BROADCAST    C                   5                            
         D*                                          record debug informatio
         D SO_DEBUG        C                   10                           
         D*                                          just use interfaces,   
         D*                                          bypass routing         
         D SO_DONTROUTE    C                   15                           
         D*                                          error status           
         D SO_ERROR        C                   20                           
         D*                                          keep connections alive 
         D SO_KEEPALIVE    C                   25                           
         D*                                          linger upon close      
         D SO_LINGER       C                   30                           
         D*                                          out-of-band data inline
         D SO_OOBINLINE    C                   35                           
         D*                                          receive buffer size    
         D SO_RCVBUF       C                   40                           
         D*                                          receive low water mark 
         D SO_RCVLOWAT     C                   45                           
         D*                                          receive timeout value   
         D SO_RCVTIMEO     C                   50                            
         D*                                          re-use local address    
         D SO_REUSEADDR    C                   55                            
         D*                                          send buffer size        
         D SO_SNDBUF       C                   60                            
         D*                                          send low water mark     
         D SO_SNDLOWAT     C                   65                            
         D*                                          send timeout value      
         D SO_SNDTIMEO     C                   70                            
         D*                                          socket type             
         D SO_TYPE         C                   75                            
         D*                                          send loopback           
         D SO_USELOOPBACK  C                   80                            
     

In addition to all of those options that you can set, there is one option that accepts a data structure as a parameter. That's the 'SO_LINGER' option. So, to be complete, we'll also add the linger structure to our SOCKET_H header member:

         D p_linger        S               *                        
         D linger          DS                  BASED(p_linger)      
         D   l_onoff                     10I 0                      
         D   l_linger                    10I 0                      
     

If you're interested in seeing the C definitions for all of these items that we just added, you can find them in the following members in your QSYSINC ("System Openness Includes") library:

      File: QSYSINC/SYS     Member: SOCKET
      File: QSYSINC/NETINET Member: IN
      File: QSYSINC/NETINET Member: TCP
     

Obviously, due to the large number of options that can be set using the setsockopt() API, the method of calling it will vary quite a bit. However, here are 3 different examples:

         D value           S             10I 0
         D len             S             10I 0
         D ling            S               *
    
    
          *** Change datagram TTL to 16 hops:
          ***
         c                   eval      value = 16
         c                   if        setsockopt(s: IPPROTO_IP: IP_TTL:
         c                                %addr(value): %size(value)) < 0
         C* setsockopt() failed, check errno
         c                   endif
    
    
          *** Allow re-use of port in bind():
          ***   (note that 1 = on, so we're turning the 're-use address'
          **      capability on)
         c                   eval      value = 1
         c                   if        setsockopt(s: SOL_SOCKET: SO_REUSEADDR:
         c                                %addr(value): %size(value)) < 0
         C* setsockopt() failed, check errno
         c                   endif
    
    
    
          ** Make space for a linger structure:
         c                   eval      len = %size(linger)
         c                   alloc     len           ling
         c                   eval      p_linger = ling
    
          *** tell the system to discard buffered data 1 minute
          ***  after the socket is closed:
         c                   eval      l_onoff = 1
         c                   eval      l_linger = 60
    
         c                   if        setsockopt(s: SOL_SOCKET: SO_LINGER:
         c                                      ling: %size(linger)) < 0
         C* setsockopt() failed, check errno
         c                   endif