How does my network interface get an IP address, and is a generated address ok to use?

This article has some good concepts about IP V6 addresses.

What addresses does an interface have?

An interface (think end of an Ethernet cable) typically has one IP V4 address which is usually manually assigned to it, and/or multiple IPV6 addresses.

An interface can have multiple IPV6 addresses – why?

You can explicitly define it

You can assign your own IP address(es) to the interface. You can do this so it has a global address, reachable from other networks.

Dynamic Host Configuration Protocol (DHCP)

If you are using a DHCP client, it communicates with a DHCP server which gives it configuration information, such as an IP address, and subnet, the client then configures the interface.

There has been a shortage of IP V4 addresses for many years. Consider an office which has a “drop-in area”. Rather than give each of the potential uses an IP address, the office is given as many IP addresses as there are desks in the drop-in area. This means perhaps 10 ip addresses are needed instead of 100. This is the advantage of DHCP.

For client devices this is fine. Your machine connects to the server and passes its IP address. The server does some work and sends the response back to the requester.

Tomorrow you may get a different IP address. It works. This means no IP address information is saved across different days. It is stateless.

A server needs either

  • a fixed IP address so clients can contact it,
  • a dynamic address, and an update to the DNS router to say “today megabank.com is on IP address 9.99.99.1”. It can take time to propagate an IP address across the worldwide DNS network.

IPv6 Stateless Address Auto-configuration (SLAAC)

The ideas behind DHCP have been extended in IPV6, the concepts are similar. Stateless Address Auto-configuration is well documented here

Within a network or router domain the system can generate an address, and it is only used within this domain, it could have a different address every time the interface is started. This is known as Stateless Address Autoconfiguration.

When an interface is started it generates an “internal use” address composed of FE00 + a mangled MAC address.

The interface then asks all devices on the local network, does anyone have this address FE00… This is for Duplicate Address Detection (DAD). There should be no reply. If there is a reply, then there is a duplicate address on the network (and the interface generates another address and repeats the process).

The interface then sends out a Router Solicitation request asking “Are there any routers out there?”. A router will then reply giving information. It will contain the “global IP prefix” such as 2001:db8::. which means to the outside world, my the address of the router is 2001:db8::/64. From this information plus the MAC address the interface can generate its IP address. The router also contains the address of the gateway (the address of the router with connections to the outside world) so traffic can now be routed externally.

This means you configure the router, and not the individual devices. If you have many devices this can save you a lot of work. If you change the router configuration, it is propagated to all the devices attached to it.

IPV6 privacy extensions will generate a “random address” (within the subnet). This is to prevent bad actors from monitoring traffic and using your IP address to monitor what sites you visit. By having a different IP address every day means they cannot correlate the traffic for a user.

Does it matter if my address is auto generated?

This is another of the “it depends” answers.

For machines that initiate work, it may not matter that the allocated IP address is different everyday or the same every day. Your IP address is passed with the request to the server. The server does the work, and sends the response back through the network. Assuming the network is configured properly the response will get back to you.

If your machine is a server machine, clients need to know the server’s address. If this changes your clients may need to use DNS to find the new address, and not use the dotted IP address. You may want to allocate a permanent IP address (within the router’s subnet).

Routers, firewalls and filters.

If your machines address is within the router’s subnet, traffic should be able to get to your router and so to your machine. If you change the subnet, traffic may not get to your router.

A firewall can be configured to allow traffic to and from specified addresses (and ports). If you use a different address, either at client or the server, the firewall may not let your packets through. Similarly with a network filter.

I was playing around with configuring IP V6 on my laptop, and the connection to z/OS failed. This was because I had been using one IP address, which I could see flowing to the back-end. When I tried some other configuration, there were more IP addresses allocated to the client, and a different IP address was used as the originator’s IP address in the ping request. The back-end server did not know how to route traffic back to this address, and so the return packets were thrown away and the ping timed out.

You need to be aware which addresses are used for your work. With some IP programs you can bind to a specific local IP address, and force the application to use a particular IP address. For example the Linux Ping command -I interface option.

CS IP filtering: writing a program to use Network management interfaces

You can write a program to get network management information from TCPIP by using the Network Management Interface API interface. It may be just as easy to use the IPSEC -f display command, and post process the output.

The Network Management Interface (nmi) is documented here (and links from this page) but it is not very easy to follow and get working. Some of the facts are there, but a lot is left to poor end user to determine how it works.

Overall the program is pretty simple

  • Connect to the server
  • Receive the init flow (the header) from the server
  • Send a request to the server
  • Receive the data from the server – this is where it gets a little tricker.

The make file

It took me a while to get my program to compile. Here is the make file I used

# Our first Makefile 
cparmsa= -Wc,"SSCOM,DEBUG,DEF(MVS,_ALL_SOURCE),UNDEF(_OPEN_DEFAULT,_NO_PROTO)
cparmsb= ,SO,SHOW,LIST(client.lst),XREF,ILP32,DLL,SKIPS(HIDE),LANGLVL(EXTENDED)"
syslib= -I'/usr/include' -I'/usr/include/sys'
all: main
parts = client.o
main: $(parts)
cc -o client $(parts)

%.o: %.c
cc -c -o $@ $(syslib) $(cparmsa)$(cparmsb) -V $<
clean:
rm *.o

It took time to find I needed UNDEF(_OPEN_DEFAULT,_NO_PROTO) . _NO_PROTO prevents function prototypes from being generated. As I wanted function prototypes, I had to UNDEF it!

My C program is was in file client.c

Include files

#define _OPEN_SYS_SOCK_IPV6 
#define _XOPEN_SOURCE_EXTENDED 1
#define _OPEN_SYS_SOCK_EXT
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <ezbnmsec.h>
#include <sys/un.h>
#include <stdarg.h>
#include <iconv.h>
#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <unistd.h>
#include <//'COLIN.C.SOURCE(PRINTHEX)'> 

Connect to the server

This is a standard TCP/IP send/receive application using AF_UNIX and a socket stream.

int    sd=-1, rc; 
struct sockaddr_un serveraddr;
sd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sd < 0)
{
perror("socket() failed");
break;
}
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sun_family = AF_UNIX;
#define SERVER_PATH "/var/sock/ipsecmgmt"
strcpy(serveraddr.sun_path, SERVER_PATH);

/********************************************************************/
/* Use the connect() function to establish a connection to the */
/* server. */
/********************************************************************/
rc = connect(sd, (struct sockaddr *)&serveraddr, SUN_LEN(&serveraddr));
if (rc < 0)
{

perror("connect() failed");
break;
}

My nmi request structure

I used

struct     { 
struct _NMsecMessageHdr header;
struct _NMsecRecordHdr record;
struct _NMsecSecDesc section;
struct _NMsecInFilter filter ;
} nmi;

I could use this to send a request to IKED, to receive data from the response to the initial connect.

Get the initial data from the server

After the connect request, the server sends back an init data stream into the nmi structure.

if (recv(sd,&nmi.header, sizeof(nmi.header), 0) < 0) 
{
tcperror("Recv()");
exit(6);
}
printf("Return code:%d\n",nmi.header.NMsMRc);
if (nmi.header.NMsMRc != 0)
{
printf("Reason code:%d\n",nmi.header.NMsMRsn);
printf("Message type:%hd\n",nmi.header.NMsMType);
printf("NMI version :%hd\n",nmi.header.NMsMVersion);
}
printf("==============\n");

Setting up input structure

I initialised the nmi structure using

      memset(&nmi.filter,0,sizeof(nmi.filter));       
      nmi.header.NMsMIdent = NMsec_MSGIDENT ;
nmi.header.NMsMHdrLength = sizeof(nmi.header);
nmi.header.NMsMVersion = NMsec_VERSION2;
nmi.header.NMsMMsgLength = sizeof(nmi);

// output record descriptor
memset(&nmi.header.NMsMOutRec ,0,sizeof(nmi.header.NMsMOutRec));

// input record descriptor
nmi.header.NMsMInRec.NMsIROffset = offsetof(nmi,record) ;
nmi.header.NMsMInRec.NMsIRRsvd1 = 0;
nmi.header.NMsMInRec.NMsIRNumber = 0; // start with none

      // need tcpip address space padded with blanks
  memset(&nmi.header.NMsMTarget,' ',sizeof(nmi.header.NMsMTarget));
  memcpy(&nmi.header.NMsMTarget,"TCPIP",5);

// clear the record
memset(&nmi.record,0,sizeof(nmi.record));

// record definition
nmi.record.NMsRIdent = NMsec_RECIDENT;
nmi.record.NMsRLength= sizeof(nmi.record) + sizeof(nmi.section) + sizeof(nmi.spare);
nmi.record.NMsRNumCascadeSecDesc = 0;
nmi.record.NMsRNumSecDesc = 1; // 1 section

// section
nmi.section.NMsSOffset = sizeof(nmi.spare ); // offset from start of record
nmi.section.NMsSLength = sizeof(nmi.spare);
nmi.section.NMsSNumber = 0;
//
// specify the request
//
// nmi.header.NMsMType = NMsec_GET_IPFLTCURR;
// nmi.header.NMsMType = NMsec_GET_SUMMARY;
// nmi.header.NMsMType = NMsec_GET_STACKINFO;
// nmi.header.NMsMType = NMsec_GET_IPFLTDEFAULT ;
// nmi.header.NMsMType = NMsec_GET_IPFLTCURR ;
// nmi.header.NMsMType = NMsec_GET_IPFLTPOLICY ;

Build the request

We were passed in the request type ( option 1 to 6) and an optional filter name in pFilterName;

If we have a filter name, we need to set the number of headers, number of sections, and number of filters – they were all set to 0.



// process the input parameters
int type = NMsec_GET_SUMMARY ;// preset this
char * pFilterValue = "";   // no filter

if (argc >= 2 ) type = atoi(argv[1]);
if (argc >= 3 ) pFilterValue = argv[2];


nmi.header.NMsMType = type;
// we can have an optional filter for these requests
if ((nmi.header.NMsMType == NMsec_GET_IPFLTCURR
||nmi.header.NMsMType == NMsec_GET_IPFLTDEFAULT
||nmi.header.NMsMType == NMsec_GET_IPFLTDEFAULT )
&& strlen(pFilterValue) > 0
)
{
if ( strlen(pFilterValue) > sizeof(nmi.filter.NMsFltObjName))
printf("Filter name %s is too long \n",pFilterValue);
else
{
// set up the filter
memset(&nmi.filter.NMsFltObjName,' ',sizeof(nmi.filter.NMsFltObjName));
memcpy(&nmi.filter.NMsFltObjName,pFilterValue,strlen(pFilterValue));
nmi.header.NMsMInRec.NMsIRNumber = 1; // one header record
nmi.record.NMsRNumSecDesc = 1; // 1 section
nmi.filter.NMsFltFlagObjNam = 1; // one filter
}
}

and send it

if (send(sd,&nmi       , sizeof(nmi       ), 0) < 0) 
{
printf("send failed\n");
tcperror("Send()");
exit(5);
}
printf("send done\n");

Receiving the response

This gets a little more complex. You cannot send an nmi structure larger than about 10KB, but the data returned can be much larger than this!

You need to peek at the data in the buffer, and allocate a buffer big enough for the returned data, then receive the data

Initial set up for receive

struct inData  { 
struct _NMsecMessageHdr header;
struct _NMsecRecordHdr record;
struct _NMsecSecDesc section;
char spare[1024 ];
} ;


struct inData * pInData;
char * pInBuffer;
int lInBuffer =1024;
pInBuffer = malloc(lInBuffer);
if (pInBuffer == 0)
{
printf("Malloc failed\n");
exit(12);
}
pInData = (struct inData * ) pInBuffer;

Use the MSG_PEEK option of recv.

int lRecv = 0; 
lRecv = recv(sd,pInBuffer,lInBuffer, MSG_PEEK);
if (lRecv < 0) // there was a problem
{
printf("recv failed\n");
tcperror("Recv()");
exit(6);
}
// look at the length of the data from the peek buffer
// if it is larger than our buffer reallocate buffer
if (pInData -> header. NMsMMsgLength > lInBuffer)
{
printf("Buffer not big enough - making it %d\n",pInData -> header. NMsMMsgLength);
lInBuffer = pInData -> header. NMsMMsgLength; // use the received length
free(pInBuffer);
pInBuffer = malloc(lInBuffer);
if (pInBuffer == 0)
{
printf("Malloc failed\n");
exit(12);
}
pInData = (struct inData * ) pInBuffer;
}

and now the real receive

// now do the real receive 
lRecv = recv(sd,pInBuffer,lInBuffer , 0 );
if (lRecv < 0)
{
printf("rec failed\n");
tcperror("Recv()");
exit(6);
}
printf("Length received %d\n",lRecv);
// set up defensive programming
char * pEnd;
pEnd = pInBuffer + lRecv ;

Check it worked

printf("Return code:%d\n",pInData->header.NMsMRc); 
printf("Reason code:%d\n",pInData->header.NMsMRsn);
printf("Message type:%hd\n",pInData->header.NMsMType);

if (InData->header.NMsMRc > 0)
{
// error handling
}

Process the data

printf("Output total  %d actual %d\n",pInData->header.NMsMOutRec.NMsORTotal 
  , pInData->header.NMsMOutRec.NMsORNumber);

char * pCurr; // current position in the output buffer.
struct _NMsecRecordHdr * pRecord ;
struct _NMsecSecDesc * pSection;
int nRecords = pInData->header.NMsMOutRec.NMsORNumber;
printf("nRecords %d\n",nRecords);
// output record descriptor locates the first output record
pRecord = (struct _NMsecRecordHdr *)
(pInBuffer + pInData->header.NMsMOutRec.NMsOROffset);
for (int rLoop = 0;rLoop <nRecords;rLoop ++)
{
pCurr = (char *) pRecord;
// defensive programming
if ( pCurr > pEnd)
{
    printf(" Record ran off the end of the buffer \n");
  leave = 1;
  break;
}
printf("Record number %d\n",rLoop);
  // section follows the header
  pCurr += sizeof(pInData->record); // size of header
  pSection = (struct _NMsecSecDesc * ) pCurr;
  // for number of sections defined in the record header
 for (unsigned short sLoop = 0; sLoop < pRecord -> NMsRNumSecDesc ; sLoop ++)
 {
char * pData = (char *) pRecord + pSection-> NMsSOffset;
int lData = pSection-> NMsSLength;
// each section can have multiple data items
for (int sdLoop = 0; sdLoop < pSection-> NMsSNumber; sdLoop ++)
{
.... process the data
pData += lData;
} // for (int sdLoop = 0; sdLoop < pSection-> NMsSNumber; sdLoop ++)
pSection ++; // move to the next section
} // for (unsigned short sLoop = 0; sLoop < pRecord -> NMsRNumSecDesc ; sLoop ++)
 // move to the next record by jumping the length of the record
pRecord = (struct _NMsecRecordHdr *)((char *) pRecord + pRecord -> NMsRLength);
} // for (int rLoop = 0;rLoop <nRecords;rLoop ++)

Process the data

printf("Record:%d Section:%d part %d \n",rLoop,sLoop,sdLoop); 
if( pInData->header.NMsMType == NMsec_GET_IPFLTCURR )
{
printFilter(pData);
// printHex(stdout,pData,lData);
}
else
if( pInData->header.NMsMType == NMsec_GET_IPFLTDEFAULT) printFilter(pData);
else
if( pInData->header.NMsMType == NMsec_GET_IPFLTPOLICY ) printFilter(pData);
else
if( pInData->header.NMsMType == NMsec_GET_IPFLTCURR ) printFilter(pData);
else

if( pInData->header.NMsMType == NMsec_GET_SUMMARY ) printStatistics(pData);
else
if( pInData->header.NMsMType == NMsec_GET_STACKINFO ) printStackInfo(pData);
else
printHex(stdout,pData,lData);

Display the data

Process the data for example

void printStackInfo(char * pData) 
{
struct _NMsecStack * pStack = (struct _NMsecStack *) pData ;
printf(" Stack name %24.24s\n",pStack -> NMsStackName);
printf(" Configured filters %n",pStack -> NMsStackFilterCount ) ;
printf(" Defensive filters %d\n",pStack -> NMsStackDefFltCount ) ;
}

In

void printStatistics(char * pData) 
{
struct _NMsecStatistics * pStats = (struct _NMsecStatistics *) pData ;
printf(" filter packets denied %lld\n",pStats -> NMsStatFilterDeny);
printf(" filter packets discarded mismatch %lld\n",pStats -> NMsStatFilterMismatch) ;
printf(" filter packets discarded match %lld\n",pStats -> NMsStatFilterMatch ) ;
}

You need to use %lld – as the numbers are 64 bit integers.

These numbers are reset if the rule is altered.

CS IP Filtering: Trying to use FTP

With my FTP, the initial connection worked on port 21, then it switched to use a different port for the file transfer. These were not set up on my system. 

On my FTP client, I received

229 Entering Extended Passive Mode (|||1028|)

which means it was trying to use port 1028. This was not configured, and so the transfer failed.

You have to configure FTP to use a range of ports, and then configure the Policy Agent with these ports.

In the FTPD JCL I have

//FTPD   PROC MODULE='FTPD',PARMS='' 
//FTPD EXEC PGM=&MODULE,REGION=4096K,TIME=NOLIMIT,
// PARM='POSIX(ON) ALL31(ON)/&PARMS'
//CEEDUMP DD SYSOUT=*
//SYSFTPD DD DISP=SHR,DSN=TCPIP.FTP.DATA
//* SYSTCPD explicitly identifieS which file iS to be
//* uSed to obtain the parameterS defined by TCPIP.DATA.
//* The SYSTCPD DD Statement Should be placed in the JCL of
//* the Server. The file can be any Sequential data Set,
//*SYSTCPD DD DISP=SHR,DSN=TCPIP.SEZAINST(TCPDATA)
//SYSTCPD DD DISP=SHR,DSN=ADCD.&SYSVER..TCPPARMS(TCPDATA)

Within the SYSFTPD dataset you need to configure the PASSIVEDATAPORTS statement.

See setting up FTP Daemon on z/OS.

CS IP filtering: defining dynamic rules for IP traffic

To see all my blog posts on IP filtering see here.

With Communications Server(CS) on z/OS you can manage IP traffic in your TCPIP image. You can allow or deny a IP packet.

You can define

  • Default rules to allow traffic, in TCPIP Profile
  • To define a set of named rules using Policy agent. These can allow or deny access
  • Individual dynamic rules which can be automated, for example external monitors, using DMD

Dynamic rules

You can use the Unix command ipsec -F to add,delete or modify individual rules. These rules might be generated by an external monitoring scheme, which can say to devices

“we seem to have a problem – use this rule to stop traffic from this destination while the CS people investigate and update the rules in the policy agent”

an example is

ipsec -F add srcip 10.1.0.2 destip all prot icmp dir inbound mode block log yes loglimit 1 lifetime 30 -N COLIN1 -p TCPIP

This defines a rule called COLIN1 on TCPIP address space TCPIP. Note you can specify a global rule for all TCPIP address spaces, by specifying -G instead of -p TCPIP

  • Source IP address 10.1.0.2
  • Destination IP address all
  • Only ICMP prototols
  • Direction inbound
  • Mode block. You can use Simulate to allow the packet – but to write an event to the log. This is useful when building rules.
  • Log – write it to the syslogd log
  • Loglimit – this logs at an average rate of 1 event per 5 minutes – so you can avoid flooding the logs.
  • Lifetime – this rule expires after 30 minutes.

Display all dynamic rules

ipsec -F display -p TCPIP

and delete it

ipsec -F delete -N COLIN1 -p TCPIP

When you add a rule, you get information In the log such as

EZD1723I Defensive filter added: 11/21/2023 08:58:10.86 filter rule= COLIN1 ext= 1 
sipaddr= 10.1.0.2 / 32 dipaddr= 0.0.0.0 / 0 proto= icmp(1) type= all code= all fragmentsonly= no dir= inbound routing= local mode= block log= yes lifetime= 30 
userid= IBMUSER global= no loglimit= 1

Log limited output

If you specify loglimit:

The value specifies the limit of the average rate of filter-match messages generated in a 5-minute interval for a defensive filter.

You get a trace message like

EZD0838I Defensive filter packet would have been denied messages limited: 
11/21/2023 13:04:22.92 filter_rule= COLIN2 filter_ext= 1 
filter_sipaddr= 10.1.0.2 / 32  filter_dipaddr= 0.0.0.0 / 0 
filter_proto= icmp(1) type= 8 code= all  filter_fragmentsonly= no filter_dir= inbound filter_routing= local suppressed_count= 274 

This shows there were 274 suppressed messages

EZD0838I:This message is issued when limiting of filter match messages was requested for a defensive filter and at least one “packet would have been denied” message (EZD1722I) for the defensive filter was suppressed during the preceding five minutes.

Suppressed counts: The number of “packet would have been denied” messages (EZD1722I) for the defensive filter that were suppressed during the preceding five minutes.

CS IP filtering: understanding logged messages

To see all my blog posts on IP filtering see here.

If you are using Communications Server IP filtering to allow/deny IP traffic to destinations, you need the TMDR daemon running. TMDR writes messages to the syslogd which can write them to a file

The trace output is like

EZD0815I Packet denied by policy: 11/17/2023 13:09:17.00 filter rule= Colin ext= 2 sipaddr=10.1.0.2 dipaddr= 10.1.1.2 proto= icmp(1) type= 8 code= 0 -=
Interface= 10.1.1.2 (I) secclass= 255 dest= local len= 84 vpnaction= N/A
tunnelID= N/A ifcname= ETH1 fragment= N

The request

  • came from 10.1.0.2 ( the Source IP ADDRress)
  • going to 10.1.1.2 the (Destination IP ADDRess
  • The protocol was icmp. Within the IP protocol an icmp packet has type 1.
  • Type is Echo request (used to called ping), and icmp packet with type 8. See here.
  • The name of the z/OS interface definition ETH1. See NETSTAT DEVLINKS
  • The IP address of the interface 10.1.1.2
  • The packet was inbound(I)
  • There was a keyword with no value -= … this is useful when parsing the traffic, because it shows a null object.

Using the Unix System Services command

ipsec -p TCPIP -f display > a

This gave

...
FilterName:                   Colin 
FilterNameExtension:          1 
...
Direction:                    Outbound 
...
FilterName:                   Colin 
FilterNameExtension:          2 
...
Direction:                    Inbound 

From filter rule= Colin ext= 2 we can see this was inbound.

With an input file

IpFilterPolicy 
{ 
  PreDecap off 
  FilterLogging on 
  IpFilterLogImplicit yes 
  AllowOnDemand yes 
  IpFilterRule Colin 
  { 
    IpSourceAddr 10.1.1.2 
    IpDestAddr   All 
    IpService 
    { 
      Protocol all 
      Direction bidirectional 
      Routing local 
    } 
    IpService 
    { 
#  PING  ICMP type 8 
      Protocol icmp Type 8 
      Direction bidirectional 
      Routing local 
    } 
    IpGenericFilterActionRef permit 
    IpService 
    { 
# 
      Protocol icmp Type 9 
      Direction bidirectional 
      Routing local 
    } 
} 

With Direction bidirectional it creates two rules, one in bound, and one outbound.

When PING was performed – this was rule 4 – from the bold text.

CS IP filtering: understanding the display output of the ipsec -f display command

To see all my blog posts on IP filtering see here.

The list below are the fields I found interesting ( mainly because they were not “n/a”)

FilterName: COLIN1

The name comes from

  • COLIN1 comes from a defensive rule like ipsec -F add … -N COLIN1, or from the name of an Policy rule IpFilterRule.
  • SYSDEFAULTDENYRULE comes from the default default rule
  • SYSDEFAULTRULE.1 comes from the default rule .1 means the first rule in the file


FilterNameExtension: 1

There can be more than one rule from a definition. For example a Direction bidirectional will create an inbound and an outbound rule. Within an IpFilterRule there can be multiple IpService definitions, each potentially with Direction bidirectional. The extension will increase for each rule.

Type: Defensive

It can be Generic if created by Policy Agent.

DefensiveType: Stack

For Type: Defensive it can be Global or Stack. For other type it is n/a.

State: Active

This always seems active (in the current list)

Action: Defensive Block

This can be Defensive Block, Deny, or Permit.

Scope: Local

Direction: Inbound

Or outbound.


SecurityClass: 0

You can classify interfaces with a security class of 1 to 255. 0 Means not specified.

Logging: All

Indicates the logging that is to be performed when the filter is invoked. Possible values are:

  • All: A log entry is generated if data traffic is permitted or denied.
  • Permit: A log entry is generated only when data traffic is permitted.
  • Deny: A log entry is generated only when data traffic is denied.
  • None: No log entries are generated from this filter.

LogLimit: 1

Used to summarise the number of times defensive rules are logged.

Protocol: ICMP(1)

For example UDP, TCP, igmp.

SourceAddress…: 10.1.0.2

There are several fields which define the source address:

SourceAddress, such as 10.1.1.2

  • Source Address: such as 10.1.1.2
  • SourceAddressPrefix, 24 as in IpSourceAddr 10.1.1.2/24
  • SourceAddressRange, 11.2.1.2 as in IpSourceAddr 10.1.1.2-11.2.1.2
  • SourceAddressGranularity, used in dynamic tunnels and dynamic VPNs.

A value of 0.0.0.0 is all.

SourcePort: and SourcePortrange

For example SourcePortRange 600 604 gives SourcePort 600 and SourcePortRange 604.

DestAddress: 0.0.0.0

See SourceAddress above.

DestPort…:

See SourcePort above

CreateTime: 2023/11/22 17:57:26

The time it was first activated (the time the TCPIP stack created its entry).

UpdateTime: 2023/11/22 17:57:26

The time it was last changed within the TCPIP stack.

DiscardAction: Silent

Indicates the discard action for packets that are discarded as a result of this filter rule. Possible values are:

  • Silent: Packets are discarded silently.
  • ICMP: When a packet is discarded, an ICMP or ICMPv6 error is sent to the origin of the discarded packet to indicate that the packet was administratively prohibited.


FilterMatches: 0

For a defensive filter the number of times this rule has been hit.

LifetimeExpires: 2023/11/22 18:27:26

For a defensive filter the time this rule will expire.


CS IP filtering: configuring TMDR – Traffic Management Daemon Regulation

To see all my blog posts on IP filtering see here.

If you are using Communications Server IP filtering to allow/deny IP traffic to destinations, you need the TRMD daemon running. With IP filtering you can log when access is allowed/denied.

The TRMD is needed to write the log events.

TRMD started task JCL

//TRMD      PROC  OPT='-P TCPIP -d 1' 
//* 
//*   IBM Communications Server for z/OS 
//*   SMP/E distribution name: EZATRMDP 
//TRMD   EXEC PGM=EZATRMD,REGION=4096K,TIME=NOLIMIT, 
//      PARM=('ENVAR("_CEE_ENVFILE_S=DD:STDENV")/&OPT') 
//STDENV   DD * 
RESOLVER_CONFIG=//'ADCD.Z24C.TCPPARMS(TCPDATA)' 
export TZ=GMT0 
/* 
//SYSPRINT DD SYSOUT=*,DCB=(RECFM=F,LRECL=80,BLKSIZE=80) 
//SYSIN    DD DUMMY 
//SYSERR   DD SYSOUT=* 
//SYSOUT   DD SYSOUT=*,DCB=(RECFM=F,LRECL=80,BLKSIZE=80) 
//CEEDUMP  DD SYSOUT=*,DCB=(RECFM=FB,LRECL=132,BLKSIZE=132) 

I do not know what authority it needs to run. It runs as userid START1 on my ADCD system.

When you start it, it starts up a job with name TMDR1, and the “started task” TMDR ends.

You stop it using P TMDR1.

Logging

The syslogd daemon needs to be running.

In my /etc/syslog.conf file I have

*.TRMD1.*.info      /var/log/TRMD1I.%Y.%m.%d 
*.TRMD1.*.debug     /var/log/TRMD1D.%Y.%m.%d 

Both of these write the access/denied messages – you would typically just have one of these.

CS IP Filtering: testing the rules

To see all my blog posts on IP filtering see here.

To test the filtering rules you can using the traffic test option of the Unix ipsec -t command.

You need to explicitly give the IP addresses such as 10.1.0.2. Using a subnet 10.1.0.2/24 or other ways, does not work.

The icmp syntax is

For my laptop with address 10.1.0.2 to z/OS on 10.1.1.2

ipsec -t 10.1.0.2 10.1.1.2 icmp -p tcpip > a

You cannot specify the icmp type.

This gave a lot of information, most of which is not very interesting , for example

FilterName:                   icmpinspecific 
FilterNameExtension: n/a
GroupName: n/a
LocalStartActionName: n/a
VpnActionName: n/a
TunnelID: 0x00
Type: Generic
DefensiveType: n/a
State: Active
Action: Permit
Scope: Local
Direction: Inbound
OnDemand: n/a
....

Using the ISPF macro na (below) this removes boring information, and gave for each rule

FilterName:                   icmpinspecific 
Type: Generic
Action: Permit
Direction: Inbound
Logging: All
Protocol: ICMP(1)
ICMPType: 8
SourceAddress: 10.1.0.2
DestAddress: 0.0.0.0
DestAddressPrefix: 0
DiscardAction: Silent

Searching for ICMPType this gave one record with 8 and two with All.

Note: DestAddress: 0.0.0.0 and DestAddressPrefix: 0 means that IpDestAddr was not specified in the rule.

You also need to check the return route exists from z/OS to the laptop

ipsec -t 10.1.1.2 10.1.0.2 icmp -p tcpip > a

The TCP/IP syntax is

ipsec -t 10.1.0.2 10.1.1.2 tcp 4000 443 -p tcpip > a

This is for a web server request from address 10.1.0.2 port 4000 to port 443 on address 10.1.1.2. Port 443 is the “well known (to my users) ” port for the web server. The web clients IP port is allocated by TCP – usually the first available free port.

It displays 3 records

FilterName:                   Colin443 
...
FilterName: DenyAllRule_Generated___________Inbnd
...
FilterName: DenyAllRule_Generated___________Outbnd
...

Where Colin443 is the rule I wrote.

Using the ISPF macro na (below) this removes boring information and leaves

FilterName:                   Colin443 
Type: Generic
Action: Permit
Direction: Inbound
Logging: None
Protocol: TCP(6)
SourceAddress: 10.1.0.2
SourcePort: All
DestAddress: 10.1.1.2
DestPort: 443
DiscardAction: Silent

For my web server to work I also need to have the reverse path from 10.1.1.2 port 443 to 10.1.0.2 port 4000.

ipsec -t 10.1.1.2 10.1.0.2 tcp 443 4000 -p tcpip >a
FilterName:                   Out443 
Type: Generic
Action: Permit
Direction: Outbound
Logging: All
Protocol: TCP(6)
SourceAddress: 10.1.1.2
SourcePort: 443
DestAddress: 10.1.0.2
DestPort: All
DiscardAction: Silent

ISPF macro na

This ISPF macro removes irrelevant information such as all the lines with not-applicable (“n/a”), and only the fields I thought important; FilterName: xtension:….

This macro needs to be put in a clist/rexx library. For example use the tso ISRDDN command, and look for DDNAME SYSPROC. This has a list of “clist” datasets which are available to you.

/* REXX */ 
/* macro to display only interesting field from ipsec command
*/
ADDRESS ISPEXEC
'ISREDIT MACRO'
trace o
"ISREDIT exclude all "
"ISREDIT find 'n/a' all"
"ISREDIT delete all nx "
"ISREDIT reset "
/* only display these fields */
w = "FilterName: xtension: Type Action Direction",
"Logging Protocol Address Port ******"
"ISREDIT exclude all "
do i = 1 to words(w)
"ISREDIT find '"word(w,i)"' all"
end
"ISREDIT delete all x "
"ISREDIT reset "

CS IP filtering: specifying IP addresses to Policy Agent

To see all my blog posts on IP filtering see here.

Within an IpFilterRule you specify IP addresses of the source and destination, for example

IpFilterRule GroupPort443 
{
IpSourceAddr 10.1.0.2
IpSourceAddr 10.1.0.0-10.1.0.8
IpDestAddrGroupRef zGroup
IpGenericFilterActionRef permit
IpService
{
Protocol Tcp
SourcePortRange 443
Direction inbound
Routing local
}
}

You can specify, using IpSource… or IpDest…

  • a single IP address using
    • inline
      • IpFilterRule … { IpSourceAddr 10.1.0.2 … }
    • as a reference to an out of line definition
      • IpSourceAddrRef name1 -> IpAddr name1 {Addr 10.1.0.2}
  • as a subnet using
    • inline
      • IpFilterRule … { IpSourceAddr 10.1.0.2/24 … }
    • as a reference to an out of line definition
      • IpSourceSetRef name2 -> IpAddrSet name2 {Prefix 10.1.0.224}
  • as a range using
    • as a reference to an out of line definition
      • IpSourceSetRef name3 -> IpAddrSet name3 {Range 10.1.0.2-10.1.0.6}
  • as a group, or collection of IP addresss
    • as a reference to out of line definition
      • IpSourceSetRef name4 -> IpAddrGroup name4 {inline IpAddr, IpAddr reference , Inline IpAddrSet, IP Address Setreference…}

An example of three host IP addresses in the common file is:

IpAddrGroup  zGroup 
{
IpAddr
{
Addr 10.1.0.2 ZOSA
}
IpAddr
{
Addr 10.1.0.3 ZOSB
}
IpAddr
{
Addr 10.1.0.4 ZOSC
}
}

Where the ZOSA, ZOSB, ZOSC are treated as comments because they come after the attribute-value.

This is used in the TCPIP instance configuration

IpFilterRule Group443 
{
IpSourceAddr 10.1.0.2
IpDestAddrGroupRef zGroup
IpGenericFilterActionRef permit
IpService ...

You cannot define a group in the TCPIP instance configuration file, it has to be in the

CommonIpSecConfig  //'USER.Z24C.TCPPARMS(PAGEICOM)'

of the Policy Agent Configuration file.

CS IP filtering: adding default rules

To see all my blog posts on IP filtering see here.

You can use the IP filtering component of z/OS Communications Server to allow or deny packets through a TCPIP stack.

You can define

  • Default rules to allow traffic, in TCPIP Profile.
  • Named rules using Policy agent. These can allow or deny access.
  • Dynamic rules which can be automated, for example external monitors, using DMD.

Define default rules

You can define default rules in the startup TCPIP profile – or replace them using an OBEYFILE

IPSEC LOGENable 
; Rule SourceIp DestIp Logging      Prot SrcPort   DestPort ...
  IPSECRULE 10.1.0.2 * LOG Protocol icmp Type 8 
  IPSECRULE * 10.1.0.2   LOG Protocol icmp 
ENDIPSEC 

This enables the default rules. It allow only

  • Ping traffic (icmp type 8) from 10.1.0.2 to any address
  • Any icmp from any address to 10.1.0.2. Note the response to ping is icmp type 0

Any other requests are denied.

A rule SYSDEFAULTDENYRULE is automatically defined. This denies all packets which are not covered by other rules. If you want to log the action of this rule you need

IPSEC LOGENable LOGIMPLICIT

This logs an event like

EZD0815I Packet denied by policy: 11/21/2023 17:11:34.71 
filter rule= SYSDEFAULTDENYRULE ext= 2 
sipaddr= 10.1.0.2 dipaddr= 10.1.1.2 proto= icmp(1) type= 8 code= 0 -= 
Interface= 10.1.1.2 (I) secclass= 255 dest= local len= 84 
vpnaction= N/A tunnelID= N/A ifcname= ETH1 fragment= N 

This could produce a lot of output. NOLOGIMPLICT means do not log packets denied by this rule.

When setting this up for the first time you might want to use

IPSEC LOGENable LOGIMPLICIT
IPSECRULE * *    NOLOG Protocol * 

Then build up rules such as

IPSECRULE 10.1.0.0/24 *    LOG Protocol * 

With this you will get messages in the syslogd file, you can then build up rules to cover the valid cases – then change LOG to NOLOG

If you use the OBEYFILE, the file contents replace any existing IPSEC default. So to add or remove an entry; edit the file, and use OBEYFILE to activate it.

Display the rules using

ipsec -f display -p TCPIP -c profile

The trace record looks like

EZD0814I Packet permitted: 11/18/2023 19:00:42.59 filter 
rule=SYSDEFAULTRULE.1 ext= 2 sipaddr= 10.1.0.2 dipaddr= 10.1.1.2 
proto= icmp(1) type= 8 code= 0 -= Interface= 10.1.1.2 (I) 
secclass= 255 dest= local len= 84 vpnaction= N/A 
tunnelID= N/A ifcname= ETH1 fragment= N 

Observations on the default rules

Some of the definitions seem strange to me!

Using

IPSECRULE 10.1.0.2 10.1.1.2 LOG Protocol icmp direction inbound
IPSECRULE 10.1.1.2 10.1.0.2 LOG Protocol icmp direction outbound

This works as I expect, it allows an inbound icmp request from 10.1.0.2(my laptop) to 10.1.1.2 ( z/OS), and an outbound request from z/OS to my laptop.

If I display the rules using ipsec -f display -p TCPIP

I get

FilterName:              SYSDEFAULTRULE.1
FilterNameExtension:     1
Direction:               Inbound
Logging:                 All
Protocol:                ICMP(1)
SourceAddress:           10.1.0.2
DestAddress:            10.1.1.2

and

FilterName:   SYSDEFAULTRULE.2
Direction: Outbound
Protocol: ICMP(1)
OSPFType: n/a
TCPQualifier: n/a
ProtocolGranularity: n/a
SourceAddress: 10.1.1.2
DestAddress: 10.1.0.2

See Using Direction bidirectional drove me crazy if you were thinking of using direction bidirectional.