pfmod Packet Filter Module

Purpose

Selectively removes upstream data messages on a Stream.

Synopsis

#include <stropts.h> 
#include <sys/pfmod.h>   
   
ioctl(fd, I_PUSH, "pfmod"); 

Description

The pfmod module implements a programmable packet filter facility that may be pushed over any stream. Every data message that pfmod receives on its read side is subjected to a filter program. If the filter program accepts a message, it will be passed along upstream, and will otherwise be freed. If no filter program has been set (as is the case when pfmod is first pushed), all messages are accepted. Non-data messages (for example, M_FLUSH, M_PCPROTO, M_IOCACK) are never examined and always accepted. The write side is not filtered.

Data messages are defined as either M_PROTO or M_DATA. If an M_PROTO message is received, pfmod will skip over all the leading blocks until it finds an M_DATA block. If none is found, the message is accepted. The M_DATA portion of the message is then made contiguous with pullupmsg(), if necessary, to ensure the data area referenced by the filter program can be accessed in a single mblk_t.

IOCTLs

The following ioctls are defined for this module. All other ioctls are passed downstream without examination.

PFIOCSETF

Install a new filter program, replacing any previous program. It uses the following data structure:

typedef struct packetfilt {
      uchar   Pf_Priority; 
      uchar   Pf_FilterLen; 
      ushort  Pf_Filter[MAXFILTERS]; 
} pfilter_t;    
   

Pf_Priority is currently ignored, and should be set to zero. Pf_FilterLen indicates the number of shortwords in the Pf_Filter array. Pf_Filter is an array of shortwords that comprise the filter program. See "Filters" for details on how to write filter programs.

This ioctl may be issued either transparently or as an I_STR. It will return 0 on success, or -1 on failure, and set errno to one of:

Value Description
ERANGE The length of the M_IOCTL message data was not exactly size of (pfilter_t). The data structure is not variable length, although the filter program is.
EFAULT The ioctl argument points out of bounds.

Filters

A filter program consists of a linear array of shortword instructions. These instructions operate upon a stack of shortwords. Flow of control is strictly linear; there are no branches or loops. When the filter program completes, the top of the stack is examined. If it is non-zero, or if the stack is empty, the packet being examined is passed upstream (accepted), otherwise the packet is freed (rejected).

Instructions are composed of three portions: push command PF_CMD(), argument PF_ARG(), and operation PF_OP(). Each instruction optionally pushes a shortword onto the stack, then optionally performs a binary operation on the top two elements on the stack, leaving its result on the stack. If there are not at least two elements on the stack, the operation will immediately fail and the packet will be rejected. The argument portion is used only by certain push commands, as documented below.

The following push commands are defined:

Command Description
PF_NOPUSH Nothing is pushed onto the stack.
PF_PUSHZERO Pushes 0x0000.
PF_PUSHONE Pushes 0x0001.
PF_PUSHFFFF Pushes 0xffff.
PF_PUSHFF00 Pushes 0xff00.
PF_PUSH00FF Pushes 0x00ff.
PF_PUSHLIT Pushes the next shortword in the filter program as literal data. Execution resumes with the next shortword after the literal data.
PF_PUSHWORD+N Pushes shortword N of the message onto the data stack. N must be in the range 0-255, as enforced by the macro PF_ARG().

The following operations are defined. Each operation pops the top two elements from the stack, and pushes the result of the operation onto the stack. The operations below are described in terms of v1 and v2. The top of stack is popped into v2, then the new top of stack is popped into v1. The result of v1 op v2 is then pushed onto the stack.

Operation Description
PF_NOP The stack is unchanged; nothing is popped.
PF_EQ v1 == v2
PF_NEQ v1 != v2
PF_LT v1 < v2
PF_LE v1 <= v2
PF_GT v1 > v2
PF_GE v1 >= v2
PF_AND v1 & v2; bitwise
PF_OR v1 | v2; bitwise
PF_XOR v1 ^ v2; bitwise

The remaining operations are "short-circuit" operations. If the condition checked for is found, then the filter program terminates immediately, either accepting or rejecting the packet as specified, without examining the top of stack. If the condition is not found, the filter program continues. These operators do not push any result onto the stack.

Operation Description
PF_COR If v1 == v2, accept.
PF_CNOR If v1 == v2, reject.
PF_CAND If v1 != v2, reject.
PF_CNAND If v1 != v2, accept.

If an unknown push command or operation is specified, the filter program terminates immediately and the packet is rejected.

Configuration

Before using pfmod, it must be loaded into the kernel. This may be accomplished with the strload command, using the following syntax:

strload -m pfmod

This command will load the pfmod into the kernel and make it available to I_PUSH. Note that attempting to I_PUSH pfmod before loading it will result in an EINVAL error code.

Example

The following program fragment will push pfmod on a stream, then program it to only accept messages with an Ethertype of 0x8137. This example assumes the stream is a promiscuous DLPI ethernet stream (see dlpi for details).

#include <stddef.h>   
#include <sys/types.h>   
#include <netinet/if_ether.h>     
#define scale(x)        ((x)/sizeof(ushort))      
setfilter(int fd)            
{               
    pfilter_t filter;                
    ushort *fp, offset;
                                          
    if (ioctl(fd, I_PUSH, "pfmod"))                              
              return -1;                                           
    offset = scale(offsetof(struct ether_header, ether_type));   
    fp = filter.Pf_Filter;                                       
    
   /* the filter program */                                     
   *fp++ = PF_PUSHLIT;                                          
   *fp++ = 0x8137;                                              
   *fp++ = PF_PUSHWORD + offset;                                
   *fp++ = PF_EQ;                                               
    
   filter.Pf_FilterLen = fp - filter.Pf_Filter;                 
   
   if (ioctl(fd, PFIOCSETF, &filter))                           
              return -1;                                           
   return 0;                                                    
}  

This program may be shortened by combining the operation with the push command:

 *fp++ = PF_PUSHLIT;                                          
 *fp++ = 0x8137;                                              
 *fp++ = (PF_PUSHWORD + offset) | PF_EQ;    

The following filter will accept 802.3 frames addressed to either the Netware raw sap 0xff or the 802.2 sap 0xe0:

offset = scale(offsetof(struct ie3_hdr, llc));               
*fp++ = PF_PUSHWORD + offset;   /* get ssap, dsap */         
*fp++ = PF_PUSH00FF | PF_AND;   /* keep only dsap */         
*fp++ = PF_PUSH00FF | PF_COR;   /* is dsap == 0xff? */       
*fp++ = PF_PUSHWORD + offset;   /* get ssap, dsap again */   
*fp++ = PF_PUSH00FF | PF_AND;   /* keep only dsap */         
*fp++ = PF_PUSHLIT  | PF_CAND;  /* is dsap == 0xe0? */       
*fp++ = 0x00e0;     

Note the use of PF_COR in this example. If the dsap is 0xff, then the frame is accepted immediately, without continuing the filter program.