Setting up for PING can be difficult!

  • Setting up ping within one machine is trivial
  • Setting up ping between two machines is relatively easy
  • Setting up ping with 3 machines can be hard to get right and to get working.

Most documentation describes how to set up PING between two machines, and does not mention anything more complex.

Ancient history (well 1980’s)

To understand modern TCPIP networks, it helps to know about the origins of Ethernet.

Originally with Ethernet there was a bus; a very long cable. You plugged your computer into this bus. Each computer on the bus had a unique 48 bit MAC address. You could send a request to another computer on this bus using their Ethernet address. You could send a request to ALL computers on this bus. These days this might be, send to all computers:”does anyone have this IP address…?”

An Ethernet bus is connected to an Ethernet router which can route packets to other routers.

Ethernet has evolved and instead of a long bus shared by all users, you have a switch device, and you plug the Ethernet cable from your computer to the switch. Conceptually it is the same, but with the switch you get much better performance and usability. You still have routers to get between different Ethernet environments. The request send to all computers:”does anyone have this IP address…?” goes to the switch, and the switch sends it to all plugged in computers.

When you start TCPIP, it configures the Ethernet hardware adapter with the addresses is should listen for.

Other terminology and concepts

  • A router is a networking device which forwards packets between different networks. Packets of data get sent from the originator through zero or more routers to the final destination.
  • A gateway has several meanings
    • It can connect different network types, for example act as a protocol translator, it can have a built-in firewall
    • It can route IP traffic between different IP subnets
    • It can loosely mean a router

Usually a router or gateway is a dedicated device or hardware.

You can have a computer act as a router or gateway; it takes data from one interface and routes it to a different interface. The computer can pass the data through a fire wall, or transform it, for example converting internal IP addresses to external IP addresses (NAT translation).

At a concept level, a computer’s network adapter is configured only to listen for packets which match the IP addresses of the interface, and ignore the rest.

If you want a computer to act as a router or gateway, you need to configure the network adapter to listen to all traffic, and to process the traffic. This is important when routing traffic from computer A through computer B to computer C.

A simple one hop ping, first time

I have set up my laptop to talk to a server over Ethernet.

I have configured my laptop using

sudo ip -6 addr add 2001:7::/64 dev enp0s31f6

This says for any IP V6 address starting with 2001:0007:0000:0000 then try sending it down the Ethernet cable with interface name enp0s31f6.

Ping a non existing address

If I try to ping an address say 2001:7::99 a packet is sent to all computers on the Ethernet bus

from myipAddress to everyone on the Ethernet bus, does anyone have IP address 2001:7::99?

There is no reply because no computer on the bus, has the address.

Ping the adjacent box

On the server, the IP address of the Ethernet cable is 2001:7::2.

If I ping this address, there are the following flows

From myipAddress (and myMAC) to everyone on the Ethernet bus, does anyone have IP address 2001:7::2?
From 2001:7::2 to myipAddress (MAC), yes I have that IP address. My Ethernet MAC address is …

My laptop puts the remote IP address and its MAC address into its neighbour cache, then issues the ping request.

The ping request looks in the neighbour cache find the IP address and MAC address and issues.

From myipAddress to 2001:7::2 at macAddress .. ping request.

The second and later times I issue a ping

The ping request looks in the neighbour cache find the IP address and MAC address and issues the ping

From myipAddress to 2001:7::2 at macAddress .. ping request.

So while the neighbour cache still has the IP of the target, and it’s MAC address, the ping can be routed to the next hop.

Setting up a multi hop ping

I have my laptop connected via a physical Ethernet cable to my server. The server is connected to z/OS via a virtual Ethernet connection. The IP address of the z/OS end of the virtual Ethernet cable is 2001:7::4.

The ping 2001:7::4 request on my laptop, does not work (as we saw above), because TCP asked everyone on the Ethernet bus if it has address 2001:7::4 – and no machine replies.

You need to define the link to the server machine as a gateway or router-like device which can handle IP addresses which are not on the Ethernet bus. You define it like

sudo ip -6 route add 2001:7::/64 via 2001:7::2

This says for any traffic with IP address 2001:0007:0000:0000:* send it via address 2001:7::2.

This requires 2001:7::2 to be known about, and so needs the following to be configured first

sudo ip -6 route add 2001:7::2 dev enp0s31f6

for TCPIP to know where to send the traffic to.

The route add 2001:7::2 dev enp0s31f6 command sends a broadcast on the Ethernet bus asking – does anyone have 2001:7::2. My server replies saying yes I have it. This is the same as for the ping above.

In summary

To send traffic on the same Ethernet bus you use

sudo ip -6 route add 2001:7::2 dev enp0s31f6

To route it via a router, switch, or computer acting as a router or switch you need.

sudo ip -6 route add 2001:7::/64 via 2001:7::2 or
sudo ip -6 route add 2001:7::/64 via 2001:7::2 <dev enp0s31f6>

The computer acting as a router or switch may need additional configuration. For example to allow it to route traffic from one Ethernet bus to another Ethernet bus you need to enable packet forwarding. On Linux to enable forwarding for all interfaces use

sysctl -w net.ipv6.conf.all.forwarding=1

On z/OS you use a static route such as

ROUTE 2001:7::/64 2001:7::3 JFPORTCP6

Where 2001:7::/64 is the range of addresses 2001:7::3 is (one of) the addresses of the interface at the remote end of the cabl, and JFPORTCP6 is the interface name. This is similar to the Linux route statement above.

You might need to set up the firewall. On my server I needed

sudo ufw route allow in on eno1 out on tap2

What IP Address does the sender have?

This is where is starts to get more complex.

Every network connection has at least one IP address.

With IP V6

  • Each interface gets an “internal” IP address such as fe80::9b07:33a1:aa30:e272
  • You can allocate an external address using the Linux command sudo ip -6 addr add 2001:7::1 dev enp0s31f6
  • If the interface has one external IP address configured, then this will be used.
  • If the interface has more than one external address configured then the first in the list may be used (not always).
  • If the interface does not have an external IP address, then TCPIP will find one from another interface, or allocate one for the duration of the request, such as 2a00:23c5:978f:9999:210a:1e9b:94a4:c8e. This address comes from my wireless connection

When my laptop is started, only the wireless connection has an IPV6 address. A ping request to 2001:7::4 had the origin IP address of 2a00:23c5:978f:9999:210a:1e9b:94a4:c8e which is the first address in the list for the wireless connection.

You can tell ping which address to use, for example

ping -I 2a00:23c5:978f:9999:cff1:dc13:4fc6:f21b 2007:1::4 (this fails because the server does now know to send the response to the requester)

I defined a new address for the interface using

sudo ip -6 addr rep 2002:7::1 dev enp0s31f6

I could issue ping -I 2002:7::1 2001:7::4, but this failed to get a response, because the back-end and intermediate nodes, did not know how to get the response back to 2002:7::1

Does this sender address matter?

Yes, because the remote end needs to have a definition to be able to send the response back.

I had my laptop connected to a Linux server over Ethernet, which in turn was connected to z/OS over a virtual Ethernet.

On z/OS I could see the ping request arrive, but it could not send the response back because it did not know how to get to 2a00:23c5:978f:9999….

I configured the laptop end of the Ethernet to give it an IP address 2001:7::1

sudo ip -6 addr add 2001:7::1/64 dev enp0s31f6

I configured the server to laptop to have an IP address of 2001:7::2

sudo ip -6 route add 2001:7::1/128 dev eno1

I configured the server to z/OS with an IP address and a route

sudo ip -6 addr add 2001:7::3/128 dev tap2
sudo ip -6 route add 2001:7::4/128 dev tap2

Now when I did the ping, the originator was 2001:7::1.

I configured the z/OS interface to send stuff back

INTERFACE JFPORTCP6 
DEFINE IPAQENET6
CHPIDTYPE OSD
PORTNAME PORTC

INTERFACE JFPORTCP6
ADDADDR 2001:DB8:8::9
INTERFACE JFPORTCP6
ADDADDR 2001:DB8::9
INTERFACE JFPORTCP6
ADDADDR 2001:7::4

START JFPORTCP6

and the routing

BEGINRoutes 
; Destination Gateway LinkName Size
ROUTE 2001:7::/64 2001:7::3 JFPORTCP6 MTU 5000
...
ENDRoutes

This says that all traffic with destination address 2001:0007:0000:000…. send to interface JFPORTCP6. This interface is connected to a gateway with the address of the remote end of the Ethernet (so on the server) of 2001:7::3.

The server machine needs to act as a router between the different Ethernet buses. You can display and configure this using

sysctl -a |grep forwarding
sudo sysctl -w net.ipv6.conf.all.forwarding=1

Packet forwarding on z/OS

By default the z/OS interface only listens for packets with one of the IP addresses of the interface. For z/OS to be able to be a router; accept all packets, and route them to other interfaces you need:

  • IPCONFIG DATAGRAMFWD in the TCP/IP Profile
  • PRIROUTER on the Interface definition . This configures the Ethernet adapter (hardware) so If a datagram is received at this device for an unknown IP address, the datagram is routed to this TCP/IP instance.

But normally if you are running z/OS you use a cheaper, physical router, rather than use the z/OS to do your routing. It might only be people like me who run z/OS on their laptop who want to try routing through z/OS.

Real time monitor for z/OS TCPIP interface statistics

Ive put up on Github a program which provides real time statistics for z/OS TCPIP interfaces.  For example

Interface       ,HH:MM:SS,IBytes,OBytes,IB/sec,OB/sec,,IUP   ,OUP   ,,...
TAP0 ,09:11:12, 0, 0, , ,, 0, 0,,...
TAP0 ,09:11:22, 0, 0, 0, 0,, 0, 0,,
TAP0 ,09:11:32, 0, 0, 0, 0,, 0, 0,,
TAP0 ,09:11:42, 348, 348, 34, 34,, 3, 3,,
TAP0 ,09:11:52, 0, 0, 0, 0,, 0, 0,,
TAP0 ,09:12:02, 0, 0, 0, 0,, 0, 0,,
TAP0 ,09:12:12, 0, 0, 0, 0,, 0, 0,,
TAP0 ,09:12:22, 0, 0, 0, 0,, 0, 0,,
TAP0 ,09:12:32, 0, 0, 0, 0,, 0, 0,,
TAP0 ,09:12:42, 0, 0, 0, 0,, 0, 0,,

Where the values are deltas from the previous time interval

  • IBytes is input bytes during that interval
  • OBytes is output bytes
  • IB/Sec is rate of input bytes
  • OB/Sec is rate of output byte
  • IUP is count of Input Unicast packets
  • OUP is count of Output Unicast packets.

You get one output file per interface.

The output file can be downloaded and used as input to a spread sheet, to graphically display the data.

I’m working on some code to display the global TCPIP stats, but I do not think they are very interesting.  If they are interesting to you, please let me know and I’ll add my code.

Using a 31 bit parameter list in a 64 bit C program.

You can use svc99() to create //DDNAME entries in a job. I used it to dynamically allocate

//COLIN DD SYSOUT=H

from within my C program.

You cannot use a 31 bit C program in a 64 bit C program

If you try to fetch and use a 31 bit C program in a 64 bit program you get

EDC5256S An AMODE64 application is attempting to fetch() an AMODE31 executable. (errno2=0xC4070068)

It is hard to make this to work, because of 64 bit parameters do not work in a 31 bit program. The save area’s are complex. Overall it is easier to just call a 64 bit program from a 64 bit program and do not try to use a 31 bit. You can do it, but you need some assembler glue code.

svc99 works in both 31 and 64 bit modes because at bind time

  • the 31 bit program includes a 31 bit version of svc99
  • the 64 bit program includes a 64 bit version of svc99.

The 64 bit program has come glue code to enable it to call the 31 bit program.

Using a 64 bit program with a 31 bit parameter list

Using svc99() was pretty easy from a 31 bit C program, but the documentation says The __S99parms structure must be in 31-bit addressable storage, which is a bit of a challenge.

For the 31 bit C program, the code is like

int main () { 
SVC99char1( sysoutClass, DALSYSOU,'A')
SVC99string(ddname,DALDDNAM,"COLIN")
struct __S99struc parmlist;
struct __S99rbx rbx =
{
.__S99EID = "S99RBX",
.__S99EOPTS =0xC4, // issue message before return, and use wto
.__S99EVER =0x01}; // version
memset(&parmlist, 0, sizeof(parmlist));
void *tp[3]= { /* array of text pointers */
&sysoutClass,
&ddname,
0};
int mask = 0x80000000;
memcpy(&p->tp[2],&mask,4); // make it a null address with high bit on
parmlist.__S99RBLN = sizeof(parmlist);
parmlist.__S99VERB = 01; /* verb for dsname allocation */
parmlist.__S99FLAG1 = 0x4000; /* do not use existing allocation */
parmlist.__S99TXTPP = tp; /* pointer to pointer to text units */
parmlist.__S99S99X =&rbx; // pointer to extension
int rc;
rc = svc99(&parmlist);

Where

  • SVC99char1(..) and SVC99string(…) define the parameters for SVC 99
  • struct __S99rbx rbx defines the request block extension which indicates errors to be reported on the job log
  • void tp[3] is the array of parameter for svc 99. The value can be null. The last must have the top bit on, so mask is copied in.
  • parmlist is the svc99 parameter list which points to the other data: the definition, and the request block extension.

Getting it to work in a 64 bit C program.

You have to build a parameter list where all the parameters are in 31 bit storage.

Generate the 31 bit structure

I defined a structure to contain the 31 bit elements, then used ___malloc31 to allocate 31 bit storage for it.

struct pl31 { 
struct __S99struc parmlist;
struct __S99rbx rbx;
// 31 bit equivilants of the SVC 99 parameters
struct sysoutClass sysoutClass;
struct ddname ddname ;
void * __ptr32 tp[3]; /* array of text pointers */
} pl31;

char * __ptr32 p31 =(char * __ptr32) __malloc31(sizeof(pl31));

The __ptr32 in char * __ptr32 p31= tells C this is a pointer to 31/32 bit storage.

Set up the parameter list

struct pl31 *  p ; 
p = (struct pl31 * ) p31; // set up 31 bit pointer to data
memcpy(&p->rbx,&rbx,sizeof(rbx)); // copy from static to 31 bit area
memset(&p->parmlist, 0, sizeof(p->parmlist)); // initialise to 0
p->parmlist.__S99RBLN = sizeof(p->parmlist);
p->parmlist.__S99VERB = 01; // verb for dsname allocation
p->parmlist.__S99VERB = 07; // display
p->parmlist.__S99FLAG1 = 0x4000; // do not use existing allocation
p->parmlist.__S99TXTPP =& p->tp ; // pointer to pointer to text units
p->parmlist.__S99S99X =& p->rbx ; // pointer to extension

Create the svc99 definitions in 31 bit storage

The macros SVC99string etc generate code like

struct ddname{ 
short key;
short count;
short length;
char value[sizeof("COLIN ")]; }
ddname = {0x0001,1,sizeof("COLIN ")-1,"COLIN "};

so within the 31 bit structure I could use

struct ddname ddname;

to allocate a structure the same shape as the original definition.

I then used a macro SVC99COPY(…); which copies the data from the original, static, definition into the 31 bit structure.

#define SVC99COPY(name) memcpy(&p->name,&name,sizeof(name));

Creating and initialising the rbx

Because the 31 bit storage is dynamically allocated, you cannot use structure initialisation like:

struct pl31 { 

struct __S99struc parmlist;
struct __S99rbx rbx =
{
.__S99EID = "S99RBX",
.__S99EOPTS =0xC4, // issue message before return, and use wto
.__S99EVER =0x01 // version
};

...
} pl31;

I defined rbx in the mainline code, and copied it into the pl31 structure once it had been allocated.

Set up the pointer to the definitions

p->tp[0]  = &p-> ddname      ; 
p->tp[1] = &p-> sysoutClass ;
int mask = 0x80000000;
memcpy(&p->tp[2],&mask,4);

The array of tp[] has to be initialised at run time, rather than at compile time because the 31 bit storage is dynamically allocated.

Invoke the svc99()

rc = svc99(&p->parmlist);

So overall – not too difficult.

Making the code bimodal

If you want to make a function which can be used from 31 or 64 bit programs. You need to provide two versions of the code. One compiled as a 64 bit program, the other as a 31 bit program. You could have

  • myprog() for 31 bit programs and
  • myprog64() for 64 bit programs.

or you could specify

#ifdef __LP64
#pragma map (myprog, "myprog64")
#else
#pragma map (myprog, "myprog31")
#endif
...
myprog()

At bind time you need to include the correct code, myprog64 or myprog31.

If could just use the one name, and have two object libraries, and specify the library in JCL, for example

//BIND.OBJLIB  DD DISP=SHR,DSN=COLIN.OBJLIB31 
//BIND.SYSIN DD *
INCLUDE OBJLIB(MYPROG)
NAME...

If you have used the wrong library at bind time, you may only find out a run time.

If you use the #pragma map to force it to use object names, you will find the problem at bind time, because myprog31 or myprog64 will not be found.

Using dynamic allocation (SVC99) from a C program.

I wanted to monitor TCPIP, and wanted to create a SYSPRINT file for each interface. I did not know the names of the interface before I started, but by using dynamic allocation, I can have “a print file” for each interface. I can programmatically create the following JCL

//COLIN DD SYSOUT=A
or
//MEMBER DD DSN=COLIN.PDS(MEMBER1),DISP=SHR

The SVC 99 routine can print error messages, or you can use the z/OS module IEFZB476 to have have the messages passed back to you program to process. Using IEFZB476 was an extra challenge.

Which function do I use?

C run time provides

  • dynalloc(), where you have a structure which you use to point to your data. For example ip.__ddname = “ABCD “;
  • svc99(), where you create an array of definitions with a numeric code, such {0x0001,..”COLIN”}

I initially use the dynalloc() function, but this does not allow you to display error messages. You get back an error code which you have to look up. Internally it creates and use an SVC 99 parameter list. z/OS has a program IEFZB476 which you can use to produce messages from the error codes, and the SVC99 parameter list, but as you do not have access to the SVC 99 parameter list, you cannot use this print message program.

The SVC99 was not that much more difficult to use, and I would recommend this to other people.

The svc99() can display error messages as WTO, or WTL, and you can pass the SVC99 parameter list to IEFZB476 to print or return the messages.

Basic dynamic allocation program.

You can create data sets, or mimic what JCL does, by associating a data set or file to a //DDNAME.

I’ll just set up output to the “printer” to illustrate the concepts. (//COLIN DD SYSOUT=A)

You pass a set of parameters to SVC 99 with the following format

  • A code to identify what this parameter is for. For example (see here for the list, and here for JCL -> value mapping)
    • 0X0001 (DALDDNAM) is to specify the DDNAME
    • 0x0018 (DALSYSOU) is for SYSOUT
  • A count of the options ( I set this to one)
  • The length of the data, for a string “COLIN” use the value 5.
  • The value of the data.
    • For SYSOUT, the data is the class, ‘A’, this has length 1
    • For DDNAME the data is “COLIN” and has a length of 5.

I set up some macros to make it easier to create these structures

#define SVC99char1(name, code,v)\     
struct{\
short key;\
short count;\
short length;\
char value[sizeof(v)]; \
} name = {code,1,1,v};
#define SVC99string(name, code,v)\
struct{\
short key;\
short count;\
short length;\
char value[sizeof(v)]; // this has a terminating null
} name = {code,1,sizeof(v)-1,v};

and used to create the structures. I then stored them in the array.

//  create them 
SVC99char1( sysoutClass, DALSYSOU,'A')
SVC99string(ddname,DALDDNAM,"COLIN")

//and use them. Create an array to contain the addresses
void *tp[2]= { /* array of text pointers */
&sysoutClass,
&ddname
};

The last entry needs the top bit set on. You can do what the documentation says

#define MASK 0x80000000 
...
void *tp[2]= { /* array of text pointers */
&sysoutClass,
&ddname};
tp[1] = (char *)((int unsigned) (tp[1]) | MASK); // set on top bit

The complication is that the documentation says

The __S99parms structure must be in 31-bit addressable storage. A call to svc99() with 64-bit addressable storage will result in -1 return code.

Which makes this harder.

I could not get this to work when running in a 64 bit program. I found it easier to create a null entry with the top bit turned on.

void * __ptr32  tp[3];   
...
int mask = 0x80000000;
memcpy(&p->tp[2],&mask,4);

You need an extension request block to get messages printed out on the job log, or returned to your program.

 struct __S99rbx rbx = 
{
.__S99EID = "S99RBX",
.__S99EOPTS =0xC4, // issue message before return, and use wto
.__S99EVER =0x01 // version
};

The _S99EOPTS controls what happens with the error messages:

  • X’80’ ISSUE MSG BEFORE RETURNING TO CALLER
  • X’40’ RETURN MSG TO CALLER
  • X’20’ USER STORAGE SHOULD BE BELOW 16M BOUNDARY
  • X’10’ USER SPECIFIED STORAGE KEY FOR MESSAGE BLOCKS
  • X’08’ USER SPECIFIED SUBPOOL FOR MESSAGE BLOCKS
  • X’04’ USE WTO FOR MESSAGE OUTPUT

Originally I used X’80’ + x’04’ to get the messages displayed.

I had to add X’40’ to get the messages back so I could use them as input to IEFZB476.

Define and initialise the SVC99 parmlist

#include <stdio.h>  // this contains the SVC99 parameter list
struct __S99struc parmlist;
memset(&parmlist, 0, sizeof(parmlist));
parmlist.__S99RBLN = sizeof(parmlist);
parmlist.__S99VERB = 01; /* verb for dsname allocation */
parmlist.__S99FLAG1 = 0x4000; /* do not use existing allocation */
parmlist.__S99TXTPP = tp; // pointer to pointer to text units defined above
parmlist.__S99S99X =&rbx; // pointer to extension
int rc;
rc = svc99(&parmlist);
//if (rc != 0)
printf("S99ERROR = %hu %4.4x S99INFO = %hu %4.4x \n",
parmlist.__S99ERROR,
parmlist.__S99ERROR,
parmlist.__S99INFO,
parmlist.__S99INFO);

__S99FLAG1 is described here. You can specify things like

  • Do not use an existing allocation to satisfy this request.
  • Do use an existing allocation to satisfy this request.
  • Requests that no messages be issued for this dynamic allocation.
  • Disable symbolic substitution for the current request.

You pass a pointer to the array of the addresses of the definitions. The last address has to have the top bit to indicate the last element.

I found it easiest to get the svc99 to print the error messages than trying to find them in the documentation. When I cause an error I got out in the joblog

-STEPNAME PROCSTEP    RC   EXCP   CONN       TCB       SRB  CLOCK   
-COMPILE COMPILE 00 14633 0 .03 .00 .0
-COMPILE BIND 00 325 0 .00 .00 .0
IKJ56893I DATA SET COLIN.JCL2 NOT ALLOCATED+
IGD17101I DATA SET COLIN.JCL2 107
NOT DEFINED BECAUSE DUPLICATE NAME EXISTS IN CATALOG
RETURN CODE IS 8 REASON CODE IS 38 IGG0CLEH

If the svc 99 gave return code 0, I could use the following to write some data to the file.

FILE * hFile; 
hFile= fopen("DD:COLIN","w");
if (hFile == 0) perror("hFile is zero");
else
fprintf(hFile,"colins output\n");

Processing the error messages using IEFZB476.

You need to specify __S99EOPTS with bit 0x40 set. This tells SVC99 to return the errors in the extension.

The number of messages returned is defined in rbx.__S99ENMSG. If this value is zero, there are no messages to display.

Create the control data

  struct { 
char EMFUNCT;
#define EMWTP 0x40 //WTO
char EMIDNUM ;
char EMNMSGBK ;
char EMRSV02 ;
char * EMS99RBP ; // Address of the failing SVC 99 RB
int EMRETCOD; // The SVC 99 return code
char * EMCPPLP; // used with tso putline
char * EMBUFP ; // Address of message buffers
int EMRSV03;
char * EMWTPCDP;
} EMPARMS;
memset(&EMPARMS,0,sizeof(EMPARMS));
EMPARMS.EMFUNCT = EMWTP ; // write to programmer
EMPARMS.EMIDNUM = 50; // EMSVC99 General caller with a DYNALLOC error
EMPARMS.EMNMSGBK= rbx.__S99ENMSG;
EMPARMS.EMS99RBP= (char *) &parmlist;
//EMPARMS.EMDAPLP Not used because we are svc 99
EMPARMS.EMRETCOD= rc;

You need to fetch the program and execute it

typedef int (*funcPtr)(); // define  a pointer type to point to a fetched module
funcPtr pIZFZB;
// Fetch the module and set the address in pIZFZB
pIZFZB = (funcPtr) fetch("IEFDB476"); /* load module */
if (pIZFZB == NULL) {
PERROR("ERROR: fetch failed\n");
return;
}
int rc2;
// and invoke it
rc2 = (*pIZFZB )(&EMPARMS);

printf("RC2 from format message, %i\n",rc2);
printf("rbx __S99EERR %hi, __S99EINFO, %hi\n", rbx.__S99EERR,rbx.__S99EINFO);
printf("__S99ERCO %2.2x __S99ERCF %2.2x\n", rbx.__S99ERCO,rbx.__S99ERCF);