Solstice X.25 9.2 Developer's Guide

Chapter 12 Compatibility with SunNet X.25 7.0 Sockets-Based Packet Level Interface

This chapter describes the sockets-based interface to the Solstice X.25 Packet Layer interface. In the current release, the sockets-based interface has been replaced by a streams-based interface. The sockets-based interface is supported for backward-compatibility with SunNet X.25 7.0 only. We strongly encourage you modify your existing X.25 applications to run over the streams-based interface described in the chapters of this manual.


Note -

The sockets-based interface is a source-compatible--not a binary-compatible--interface. Applications that used the socket interface in SunOS 4.x must be recompiled to run on SunOSTM 5.x. See "13.2 Compilation Instructions and Sample Programs "for instructions on compiling programs to use the sockets-based interface on SunOS 5.x.


12.1 Introduction -- The AF_X25 Domain

This chapter assumes some familiarity with SunOS sockets and address domains (families). Briefly, the socket layer of the network system deals with the interprocess communications provided by the system. A socket is a descriptor that acts as a bidirectional endpoint for communications and is "typed" by the semantics of the communications it supports. The type of the socket is defined at socket creation time and used in selecting those services which are appropriate to support it. The socket type SOCK_STREAM provides sequenced, reliable, two-way, connection-based byte streams with an out-of-band data transmission mechanism. An address domain specifies an address format which is used to interpret addresses specified in later operations using the socket.

Solstice X.25 defines an address domain, AF_X25. Within this domain only the socket type SOCK_STREAM is supported. Like other SOCK_STREAM sockets, an AF_X25 domain socket is composed of two byte streams: an in-band stream and an out-of-band stream. However, unlike other sockets, there are two different kinds of out-of-band messages: X.25 status and interrupt data.

12.2 AF_X25 Domain Addresses

Addresses in the AF_X25 domain consist of two parts: a DTE address of up to 15 BCD digits and Call User Data of up to 16 bytes. (The leading bytes of the Call User Data is often a protocol identifier [PID] used to identify a specific application using X.25.) You can use either subaddressing (part of 15-digit DTE address) or both subaddressing and Call User Data as part of the binding mechanism to match Incoming Call packets with a server process.

An AF_X25 domain address is described by a CONN_DB structure:

typedef struct conn_db_s {
    u_char   hostlen;         /*address length in BCD digits */
    u_char   host[(MAXHOSTADR+1)/2];   /* DTE address */
    u_char   datalen;         /* user data length in bytes */
    u_char   data[MAXDATA];   /* user data */
 } CONN_DB; 

The constants MAXHOSTADR and MAXDATA are defined in the include file x25_pk.h. Currently, MAXHOSTADR is 15, so the length of the host field is 8, and MAXDATA is 102. Use these constants, whenever possible, instead of hard-coded values.

The 15-digit DTE address comprises three components: a Data Network Identification Code (DNIC), a Network Terminal Number (NTN), and a subaddress. A full X.121 address is the concatenation of a DNIC, NTN, and subaddress, in that order. For example, if the DNIC is 4042, the NTN is 3831, and subaddress is 06, the full X.121 address is 4042383106.

Note that only eight bytes are provided for the X.121 address, which could be up to 15 digits in length. This is because each byte holds two BCD digits in packed format (it takes only four bits to represent a BCD digit). Thus the address 4042383106 will be stored as five bytes, with hexadecimal values 0x40, 0x42, 0x38, 0x31, and 0x06, in that order.

The necessary include files are listed in Chapter 13, Sockets Programming Example. For more information on address binding, see "12.3.4 Address Binding ".

12.3 Creating Switched Virtual Circuits

To set up a switched virtual connection between a local and remote system, a socket in the AF_X25 domain is created using the standard socket call:

int s;   /* socket to be created */
 s = socket(AF_X25, SOCK_STREAM, 0); 

If a signal handler routine is to be used, it is necessary to associate a proper process group ID with the socket. Refer to the section "12.5.4 Out-of-Band Data " of this chapter to see how this is done. X.25 facility specification and negotiation may be done after creating a socket. See "12.7.1 Facility Specification and Negotiation" of this chapter for more information regarding facility specification.

After a socket has been created, the client executes one of the two sequences described in the following subsections to set up the virtual circuit.

12.3.1 Calling Side -- Outgoing Call Setup

The calling side initiates a virtual circuit connection by calling connect, supplying the called (remote) DTE address (including subaddress, if any) and a user data field as arguments. After connect completes successfully, the socket may be used for data transfer.

int   s /* socket */, error;
 CONN_DB   addr;
 error = connect(s, &addr, sizeof(addr)); 

Solstice X.25 supports multiple physical interfaces (or links). A single link maps to a serial port device, such as zsh0.

A link is automatically selected for the outgoing call. Among multiple links, Solstice X.25 routes outgoing calls based on the called address. Calls are routed according to the full or partial addresses (X.121, or NSAP or non-NSAP extended addresses) you specify in a routes file, the syntax for which is described in Solstice X.25 9.2 Administration Guide. The lowest-numbered link is the default.

If the interface supports 1984 X.25, the user may also specify a Called Address Extension Facility (AEF). In this case, Solstice X.25 will use the Called AEF to route the call over a particular link, provided the user has not specified an X.121 address. If the user wants the call to be routed based on the Called AEF, the hostlen field should be set to zero:

addr.hostlen = 0;

Where AEFs are used for routing, Solstice X.25 will select the interface to use and will also supply the X.121 address (if any) for the Call Request packet. In addition, if it is a LAN interface, Solstice X.25 will supply the necessary LSAP address.

Called and Calling AEFs are described in the section "12.7.1 Facility Specification and Negotiation".


Note -

error is used in most examples to indicate the return code. A value of zero indicates a successful operation. A non-zero value indicates an unsuccessful operation. The cause of the error is stored in a global variable errno which is used throughout this manual. Values of errno are enumerated in <errno.h>. These values are listed in intro(2) in the SunOS Reference Manual. Programmers may access errno by inserting the following line in their programs: extern int errno; Note that errno indicates the cause of the very last system call failure and is therefore invalid for operations returning an error value of zero. To get more information on the meaning of the error string printed, use the perror function.


12.3.2 Calling Side -- Setting the Local Address

Often, the receiver of an Incoming Call needs to know the address of the caller in order to validate the call. By default, the calling address in the Call Request is set to the address (including the subaddress, if any) specified in the configuration file of the link over which the Call Request is sent. There are several parameters in the link configuration file, all described in the preceding subsection, that determine how Solstice X.25 preprocesses the calling address to satisfy the requirements of the interface.

You may specify a different address using the X25_WR_LOCAL_ADR ioctl. The address is specified in a CONN_ADR structure.

typedef struct conn_adr_s {
    u_char   hostlen;   /* length of BCDs */
    u_char   host[(MAXHOSTADR+1)/2];
 } CONN_ADR;

Here, as in the CONN_DB structure, hostlen is the length of the address in BCD digits, and host contains the address in packed BCD format. The X25_WR_LOCAL_ADR ioctl call is issued as follows:

CONN_ADR addr;
 int s, error;
 error = ioctl(s, X25_WR_LOCAL_ADR, &addr); 

The setting of the source address--and whether the X25_WR_LOCAL_ADR ioctl has effect--is controlled by the setting of the Source Address Control parameter in the Link Mode Parameters window in x25tool. See Solstice X.25 9.2 Administration Guide for instructions on setting this parameter.

12.3.3 Called Side -- Incoming Call Acceptance

The called side initiates listening for incoming calls by calling bind, supplying the called (local) DTE address (including subaddress, if any) and protocol identifier to be used for matching with incoming calls:

int s, error;
 CONN_DB bind_addr;
 error = bind(s, &bind_addr, sizeof(bind_addr));

Here, bind_addr contains the address and protocol identifier of the called side. The protocol identifier is specified in the data field of the CONN_DB structure and is matched with the user data in incoming calls. More information on how to specify the address and protocol identifier for the bind call, and how incoming calls are matched with bound addresses and protocol identifiers, follows.

After bind has been called, listen is called to begin waiting for incoming calls. Incoming calls will be queued until they are accepted by means of the accept call. backlog specifies the maximum number of incoming calls (no more than five) to queue (waiting for accept) before clearing additional incoming calls.

int s, backlog, error;
 error = listen(s, backlog);

Finally, accept is called to block until an incoming call is received that matches the address and protocol identifier specified in the bind call. accept is passed a pointer to a CONN_DB structure (and length), which will be filled in with the calling DTE's (remote) address and user data field. The user data field in an Incoming Call packet consists of a protocol identifier followed by any additional user data. After an incoming call matches the binding criteria, accept returns the socket news, to be used for data transfer. news inherits the process group ID from s.

int    s, news;
 int    from_addr_len;
 CONN_DB from;
 from_addr_len = sizeof(from);
 news = accept(s, &from, &from_addr_len);

The remote address returned in from will be exactly as received (that is, in exactly the same form as received in the calling address field in the Incoming Call packet).

Note that on entry into the accept call, from_addr_len should be set to the size of the CONN_DB structure. On return, it will be set to the length of the actual address returned in from.

A typical caller of accept would be a server process that forks a new process (after calling accept) to handle each new socket. The sample programs (see Chapter 13, Sockets Programming Example") provided with Solstice X.25 illustrate how this can be done.

12.3.4 Address Binding

When an Incoming Call packet is received by Solstice X.25, the called address and user data field are matched against all listening sockets. In addition, if the interface supports 1984 X.25, and if the listener has specified a value for the Called AEF, the Called AEF field in the Incoming Call (if any) will be matched with the Called AEF specified by the listener. If a match is found, the call is accepted and the user process associated with that socket will be notified when the user process does an accept. This permits incoming calls to be bound to the correct user process. X.25 supports binding by either address or by both address and protocol identifier. The method used is determined by the fields of the CONN_DB structure passed to bind.

The address a socket is bound to is specified in the host field of the CONN_DB parameter passed to the bind call. The address is specified in packed BCD format, and the hostlen field contains the length of the address in BCD digits.

You can specify the bound address in a number of ways, depending on whether you want to accept all calls (from any link, for any subaddress), or all calls for a specific subaddress (from any link, for a particular subaddress), or calls from a specific link for any subaddress, or calls for a specific address (from a specific link, for a specific subaddress).

If you want to accept all calls (from any link, for any subaddress), set the bits ANY_LINK (0x80) and ANY_SUBADDRESS (0x40) in the hostlen field and do not specify any address:

bind_addr.hostlen = ANY_LINK | ANY_SUBADDRESS;

If you want to accept calls from any link, but only for a specific subaddress, specify only the subaddress, and set the ANY_LINK bit in the hostlen field:

bind_addr.hostlen |= ANY_LINK;

If you want to accept calls from a specific link, but for any subaddress, specify the link address (without the subaddress) and set the ANY_SUBADDRESS bit in the hostlen field:

bind_addr.hostlen |= ANY_SUBADDRESS;

If you want to accept calls for a specific address (including subaddress) specify the exact address in the CONN_DB structure passed to bind. In this case, the address you specify must exactly match the called address field of the received Incoming Call packet. The address of a link may be obtained with an X25_RD_LINKADR ioctl call (see the section "12.7.6 Accessing the Link (X.25) Address " of this chapter for details).

The sample programs provided with Solstice X.25 illustrate the above features.

12.3.5 Binding by PID/CUDF

To bind by protocol identifier (PID), you must specify a protocol identifier in the data field of the CONN_DB parameter passed to bind. The datalen field contains the length of the protocol identifier. You can specify up to 102 bytes of protocol identifier, but only the first 16 bytes will be used for matching with user data in Incoming Call packets.

The user data field in an Incoming Call may be longer than the protocol identifier specified in bind. The match will be considered successful if the protocol identifier specified in bind is an initial sub-string of the user data in an Incoming Call. Thus, if you specify a zero-length protocol identifier in bind, it will match the user data in any Incoming Call.

You can enforce exact matching of the protocol identifier with user data in Incoming Call packets by setting the bit EXACT_MATCH (0x80) in datalen:

bind_addr.datalen |= EXACT_MATCH;

In this case, user data in an Incoming Call packet should match the protocol identifier specified in bind exactly (in content and length) in order for the match to be considered successful.

See Chapter 13, Sockets Programming Example," for references to sample code. A simple example is given below:

CONN_DB bind_addr;
 int s, error;
 /*We want to accept calls from any link, for the subaddress 01.
 * We must specify the two digit subaddress 01 and set the ANY_LINK
 * bit in the hostlen field.
 */
 bind_addr.hostlen = 2 | ANY_LINK;   /* there are 2 BCD digits */
 bind_addr.host[0] = 0x01;
 /* We will specify a protocol identifier consisting of a single byte
 * with value 0x02.
 */
 bind_addr.datalen = 1;
 bind_addr.data[0] = 0x02;
 error = bind(s, &bind_addr, sizeof(bind_addr));

12.3.6 Masking Incoming Protocol Ids at Bit Level

The user data in an Incoming Call may be masked (that is, bitwise ANDed), using a specified mask value, before it is matched with the protocol identifier specified in a bind call. The mask is specified in a MASK_DATA_DB structure using the X25_WR_MASK_DATA ioctl. Here is an example:

typedef struct mask_data_bd_s {
    u_char   masklen;
    u_char   mask[MAXMASK];
 } MASK_DATA_DB;

 MASK_DATA_DB m;
 int s, error;

 m.masklen = 3;
 m.mask[0] = 0xff;
 m.mask[1] = 0x00;
 m.mask[2] = 0xff;

 error = ioctl(s, X25_WR_MASK_DATA, &m);

MAXMASK is currently 16. masklen holds the length of the mask data in bytes, and mask is the actual mask value. In the above example, the first three bytes of user data in an Incoming Call will be masked: the first byte with 0xff, the second with 0x00, and the third with 0xff. The masked user data will then be matched with the specified protocol identifier. Note that the specified protocol identifier will not be masked before matching occurs, so in the above example, the second byte of the specified protocol identifier must be zero if any match is to succeed.

12.3.7 AEF Matching Considerations

A listener may specify a Called AEF. In this case, the Incoming Call packet must have the Called AEF, and it should match the Called AEF specified by the listener exactly, in order for the match to succeed. If the listener has not specified a Called AEF, any Called AEF present in the Incoming Call packet will be accepted, provided the match succeeds in other ways (Called Address and PID).

12.3.8 Explicit Link Selection--Calling Side

As discussed in a previous subsection, Solstice X.25 automatically selects a link for an outgoing call if so requested by the caller. If you do nothing to call automatic link selection into play, the call is sent over the lowest numbered WAN link by default. The calling side can override automatic link selection, and specify a desired link using the X25_SET_LINK ioctl:

int s, error;
 int linkid;        /* id of desired link for outgoing call */
 CONN_DB addr;      /* destination address */
 linkid = 3;        /* want to send call over link 3 */
 error = ioctl(s, X25_SET_LINK, &linkid);

 /* check error here */

 error = connect(s, &addr, sizeof(addr));

Note that a full X.121 address must be specified (and so indicated by setting the ANY_LINK bit as described earlier) if you want Solstice X.25 to process the address as required by the PSDN, using the parameters specified in the link configuration file. Otherwise, the address set in the Call Request packet will be exactly what you specified, and so you must take care to provide the address in exactly the form required by the PSDN.

Since setting the link prevents Solstice X.25 from consulting the routing table, all the information required to establish connection with the remote user must be provided. For example, if the link selected supports 1984 X.25, Called and Calling AEFs may be required. If the link selected is a LAN interface, the LSAP address of the remote user must be provided. This is done as follows:

typedef struct {
    u_char   lsel;
    u_char   maclen;
 #define MACADDR_LEN   6
    u_char   macaddr[MACADDR_LEN];
 } X25_MACADDR;

 X25_MACADDR dst_mac;         /* LSAP address */
 int s;                        /* socket */

 /* set the lsel, maclen and macaddr fields here */

 error = ioctl(s, X25_WR_MACADDR, &dst_mac);

If the lsel field is set to zero, Solstice X.25 will use the value specified in the link configuration file. After connection is established, the LSAP address of the remote user can be read using the X25_RD_MACADDR command:

X25_MACADDR dst_mac;         /* LSAP address */
 int s;                       /*socket */

 error = ioctl(s, X25_RD_MACADDR, &dst_mac);

12.3.9 Explicit Link Selection--Called Side

The called side may restrict the calls it wishes to examine for a possible match to a particular link by means of the X25_SET_LINK ioctl.

int s, linkid, error;
 CONN_DB addr;      /* address and protocol identifier */

 linkid = 2;        /* restrict calls to link 2 */
 error = ioctl(s, X25_SET_LINK, &linkid);

 /* check error here */

 error = bind(s, &addr, sizeof(addr)); 

The ANY_SUBADDRESS and ANY_LINK bits can still be used in the same way as explained in the section"12.3.4 Address Binding " of this chapter. The ANY_LINK bit, in this context, serves as an abbreviation for the link address, and you do not have to specify the link address explicitly. A zero-length address also works in the same way as described in the "12.3.4 Address Binding " section. Otherwise, you must specify the address in exactly the form it will be received. That is, it must exactly match the called address field of the received Incoming Call packet.

12.3.10 Accessing the Local and Remote Addresses

Once a connection is established, the calling and called sides may use the getsockname and getpeername calls to obtain the local and remote X.121 addresses:

int s, error;
 CONN_DB local;       /* local address */
 int local_len;       /* local address length */
 CONN_DB remote;      /* remote address */
 int remote_len;      /* remote address length */

 /* get local address */
 local_len = sizeof(local);
 error = getsockname(s, &local, &local_len);

 /* get remote address */
 remote_len = sizeof(remote);
 error = getpeername(s, &remote, &remote_len);

The local and remote addresses can also be obtained using the X25_RD_LOCAL_ADR and X25_RD_REMOTE_ADR ioctl calls:

int s, error;
 CONN_ADR local;       /* local address */
 CONN_ADR remote;      /* remote address */

 /* get local address */
 error = ioctl(s, X25_RD_LOCAL_ADR, &local);

 /* get remote address */
 error = ioctl(s, X25_RD_REMOTE_ADR, &remote);

Note that for getsockname and getpeername, the CONN_DB structure is used, and for the ioctl calls, the CONN_ADR structure is used. In both cases, the host field will contain the address in packed BCD format, and the hostlen field will contain the address length in BCD digits.

For the called side, the remote address will be defined only after the connection is complete. The remote address obtained using either of the above two methods will be exactly as obtained from the Incoming Call packet. After the call is established, the local address (obtained by either method) will be exactly as received in the called address field in the Incoming Call packet.

For the calling side, the remote address will be exactly as specified in the connect call. If the ANY_LINK bit was set in the hostlen field, it will be also set when it is read by the user using either of the above methods. The source address for the calling side will be either a zero-length address (indicating that the appropriate link address was used), or exactly what the user specified using the X25_WR_LOCAL_ADR ioctl call (including the SUBADR_ONLY bit if it is used).

12.3.11 Finding the Link Used for a Virtual Circuit

If you let Solstice X.25 select the link for an outgoing call, or make an accept call that accepts incoming calls from any link, you may use the X25_GET_LINK ioctl to obtain the identifier of the link used for the call:

int s, error;
 int linkid; /* link identifier */

 error = ioctl(s, X25_GET_LINK, &linkid);

If this call is made before connection establishment and you have not explicitly selected a link, linkid will be set to -1 on return from the call. After connection establishment, linkid will have a value in the range zero through one less than the maximum number of links configured.

An important use for this ioctl arises when the called side determines the remote address in order to call back the remote DTE. In this situation, the remote address is presented in exactly the form it arrived in the Call Request. For some PSDNs, this may not contain a DNIC. Hence, the only way you can call the remote DTE back is by finding out the link id for the call using the X25_GET_LINK ioctl, and explicitly selecting this link using the X25_SET_LINK ioctl when calling the remote DTE back. In this situation, you should not set the ANY_LINK bit in the hostlen field of the CONN_DB parameter to the connect call.

12.3.12 Determining the LCN for a Connection

To find out which logical channel is associated with a connection, do the following:

int s, lcn;
 error = ioctl(s, X25_RD_LCGN, &lcn);

Here, s is the socket associated with the connection (or virtual circuit). On return from the call, lcn is set to the logical channel number associated with socket s. If the returned value of lcn is 0, there is no connected virtual circuit associated with the socket.

12.4 Sending Data

The send call is used to send data over a virtual circuit. send is passed the socket, a pointer to the data to be transmitted, the length of the data, and a flag indicating the type of data to be sent. Interrupt data is sent by setting flags to MSG_OOB. Otherwise, flags should be set to zero. The returned count indicates the number of bytes transmitted by send.

int count, len, flags, s;
 char *msg;
 count = send(s, msg, len, flags);

Note that for normal data, you can use the write system call instead of send. The call:

write(s, msg, len) 

is equivalent to:

send(s, msg, len, 0)

The X.25 protocol has the concept of an X.25 message. A complete X.25 message is a sequence of one or more packets with the M-bit (More bit) set in all but the final packet. Normally, X.25 sends the data specified in a send call as a complete message. This means that the data will be segmented into packets as required by the PSDN, and the M-bit will be set in all but the final packet. If the user wishes to pass the data in a complete X.25 message in pieces (that is, using multiple send calls), the setting of the M-bit must be controlled using the X25_SEND_TYPE ioctl as described below.


Note -

In the current release of Solstice X.25, send() returns a positive result after a virtual circuit is closed at the remote end. This behavior is different from SunNet X.25 7.0. To be notified when the virtual circuit has been closed, use the X25_OOB_ON_CLEAR ioctl, as described in "12.7.8 Accessing the Diagnostic Code".


12.4.1 Control of the M-, D-, and Q-bits

The settings of M-, D- and Q-bits in transmitted packets are changed by means of the X25_SEND_TYPE ioctl call.

ints, send_type;
 error = ioctl(s, X25_SEND_TYPE, &send_type);

send_type provides the new settings of the M-, D-, and Q-bits. The M-, D-, and Q-bits are encoded into the send_type field by bit shifting as shown below.

#define M_BIT 0       /* number of bits to shift to set "more"
        * bit */
 #define D_BIT 2       /* number of bits to shift to set end-to-end
        * acknowledge bit */
 #define Q_BIT 3       /* number of bits to shift to set qualified
        * data bit */

For example, to set the Q-bit in a packet:

intsend_type = (1 << Q_BIT), s;
 error = ioctl(s, X25_SEND_TYPE, &send_type); 

M_BIT determines whether or not a packet is the final piece of a complete X.25 message. If M_BIT is set, subsequent send calls are treated as part of a single X.25 message. If M_BIT is not set, the next send ends the current X.25 message. For example, the following code allows a complete X.25 message to be sent in three pieces:

ints, send_type, error;
 /* Set M_BIT to indicate multiple pieces */
 send_type = (1 << M_BIT);
 error = ioctl(s, X25_SEND_TYPE, &send_type);
 /* send first piece */
 error = send(s, &first_piece, sizeof(first_piece), 0);
 /* send next piece */
 error = send(s, &second_piece, sizeof(second_piece), 0);
 /* Clear M_BIT to indicate end of message */
 send_type = 0;
 error = ioctl(s, X25_SEND_TYPE, &send_type);
 /* send final piece */
 error = send(s, &final_piece, sizeof(final_piece), 0);   

If the M-bit is turned on using the X25_SEND_TYPE ioctl, it will stay turned on until it is turned off. The X.25 recommendation states that the M-bit shall be turned on only in packets that are "full"--that is, packets that have the maximum size for that virtual circuit. So if the M-bit is turned on, and the next send does not supply a full X.25 packet, X.25 will wait until enough send calls have been issued to build a full X.25 packet before transmitting the next packet with the M-bit turned on.

The Q-bit qualifies the data in Data packets. A local DTE sets the Q-bit to indicate that the data being sent is significant for a device connected to the remote DTE. It is often used by a remote host when sending control packets to a PAD, to distinguish the control packets from packets containing user data.

The D-bit allows a local DTE to specify end-to-end acknowledgment of data packets. Normally, a DTE receives acknowledgement only from its local DCE. The D-bit is significant only in call setup and data packets.

D_BIT and Q_BIT control the settings of those bits in an X.25 packet. These bits are manipulated in the same manner as the M_BIT was above. Since the X.25 recommendation states that the D_BIT and Q_BIT bits should remain constant for each packet in a complete X.25 message, D_BIT and Q_BIT should only be changed at the beginning of an X.25 message.

Unlike M_BIT, D_BIT and Q_BIT are turned off automatically after a complete X.25 message has been sent. Hence, to set these bits in a series of complete X.25 messages, you should turn them on at the start of each complete X.25 message. If the complete X.25 message is a sequence of full packets with the more bit turned on in all but the last packet in the sequence, the setting of D_BIT and Q_BIT will be the same for all the packets unless you explicitly change the setting in between.

12.4.2 Sending Interrupt and Reset Packets

An interrupt packet may be sent in the following manner. The interrupt user data is contained in intr:

int s;
 char intr = 0;       /* set this variable to contain the interrupt
                       * user data (in this case 0) */
 error = send(s, &intr, 1, MSG_OOB);

If the link supports 1984 X.25, you may send up to 32 bytes of interrupt data. On 1980 links, you may send only one byte.

A reset packet may be sent in the following manner:

X25_CAUSE_DIAG diag;
 int error, s;
 diag.flags = 0;
 diag.datalen = 2;
 diag.data[0] = 0;          /* cause */
 diag.data[1] = 67;         /* diagnostic */
 error = ioctl(s, X25_WR_CAUSE_DIAG, &diag);

This will cause a Reset to be sent with the cause code and diagnostic specified by the user. See "12.7.8 Accessing the Diagnostic Code" of this chapter for more information.

12.5 Receiving Data

To read data from an X.25 socket, call recv. Data may be either in-band (normal data) or out-of-band (interrupt data and status). To receive out-of-band data, set flags to MSG_OOB. To receive normal data, set flags to 0.

int s, len, flags, count;
 char *buf;
 count = recv(s, buf, len, flags);

Note that for normal data, you can use the read system call instead of recv. The call:

read(s, buf, len)

is equivalent to:

recv(s, buf, len, 0)

12.5.1 In-Band Data

Calling recv with flags set to zero reads in-band data. Normally, each recv returns one complete X.25 message. It is very important to note that if the size of the receive buffer is not sufficient to hold the entire X.25 message, the excess is discarded and no error indication is returned. This is a feature of SunOS sockets, not of Solstice X.25. count returns a count of the number of bytes returned by recv. If the user wishes to read an X.25 message in pieces smaller than a complete message, the X25_RECORD_SIZE ioctl should be used as described in the section "12.5.3 Receiving X.25 Messages in Records "of this chapter.

Unless non-blocking I/O has been requested, the recv call will block unless there is some data that can be returned to the user. If the connection is cleared (due to normal or abnormal reasons) while recv is blocked, recv will return a count of zero. A return value of zero from recv is an indication that the connection has been cleared, and the user must close the socket at this point.

12.5.2 Reading the M-, D-, and Q-bits

To determine the values of the M-, D-, and Q-bits in received frames, call the X25_HEADER ioctl before the virtual circuit has been created.

ints, need_header;
 error = ioctl(s, X25_HEADER, &need_header); 

If need_header is set to one, subsequent recvs will return the data preceded by a one-byte header that contains the values of the M-, Q-, and D-bits encoded as bit shifts as follows:

#define M_BIT 0      /* number of bits to shift for M-bit */
 #define D_BIT 2      /* number of bits to shift for D-bit */
 #define Q_BIT 3      /* number of bits to shift for Q-bit */ 

For example, to check for the presence of the Q-bit in a packet, the following sequence might be used:

char buf[1025];
 int s, need_header = 1, count, error;
 error = ioctl(s, X25_HEADER, &need_header);
    . . .
 count = recv(s, buf, sizeof(buf), 0);
 if (count > 0 && (buf[0] & (1 << Q_BIT)))
    /* then Q bit is on */ 

The X25_HEADER ioctl must be issued either before the connect call (for outgoing calls), or before the accept call (for incoming calls). For PVCs, the X25_HEADER ioctl must be issued before the X25_SETUP_PVC ioctl. For the duration of the call, the X25_HEADER ioctl must not be used to change the header setting. For example, if a message is received when the header setting is on and the user turns it off before reading the message, the user will receive a one-byte header along with the message, even though he is not expecting it.

If the header is requested, X.25 does not wait for a complete X.25 message to be assembled before returning any data to the user. Rather, partial messages (indicated by the presence of M_BIT) are returned to the user as they become available. Note that the buffer supplied in the recv call must be large enough to accommodate the extra byte of header information.

12.5.3 Receiving X.25 Messages in Records

By default, each recv returns a complete X.25 message. To force recv to return data before a complete X.25 message has been assembled, issue the X25_RECORD_SIZE ioctl after the socket is created:

int s, record_size, error;
 /* Set record_size to n, where n is the number of
  * maximum size packets with more bit turned on that
  * will be received before the accumulated data is
  * returned in a recv call.
  */
 error = ioctl(s, X25_RECORD_SIZE, &record_size);

Here, record_size specifies the number of full (maximum size) packets with M-bit turned on that X.25 will receive before the accumulated data is returned to the user as a record (or message). Thus, the maximum record size seen by the user will be record_size times the maximum packet size for the virtual circuit. If a complete X.25 message comprises less than record_size packets, it will be returned to the user as in the normal case.

The X25_RECORD_SIZE ioctl is useful when complete X.25 messages are potentially very long, so that either they cannot be buffered in the socket receive buffers (limited by the high water mark), or it is too much of a performance bottleneck for the application to wait for the whole message to be assembled before processing it, or the application does not wish to dedicate very large buffers for receiving data. If record boundaries (that is, message boundaries) are important, this method must not be used. Rather, the X25_HEADER ioctl must be used, as indicated earlier, to obtain a header byte for each packet that indicates whether or not the packet is the last one in a record (that is, message).

12.5.4 Out-of-Band Data

Out-of-band data is managed by a combination of ioctl calls, the passing of the MSG_OOB flag to recv, and an optional signal, SIGURG. To determine whether out-of-band data has been received, call the X25_OOB_TYPE ioctl:

ints, oob_type;
 error = ioctl(s, X25_OOB_TYPE, &oob_type); 

If out-of-band data does not exist, oob_type is set to zero. Otherwise, oob_type is set to a value indicating the type of out-of-band data that has been received. The types of out-of-band data are:

#define INT_DATA      30   /* interrupt data */
 #define VC_RESET      32   /* virtual circuit reset */ 

INT_DATA indicates that interrupt data has been received. The interrupt data is read by calling recv with flags set to MSG_OOB. In general, the following sequence occurs upon receipt of an interrupt packet:

  1. X.25 receives an interrupt request packet. The interrupt is queued and causes a SIGURG signal.

  2. The user reads the interrupt packet (with recv), automatically causing an Interrupt Confirmation packet to be sent.

    Up to 32 bytes of interrupt data may be received if the interface supports 1984 X.25.

    It is not necessary to issue a recv call with flags set to MSG_OOB if the interrupt type is something other than INT_DATA.

    VC_RESET indicates that the virtual circuit associated with the socket has been reset.

    The SunNet X.25 7.0 interface had an additional type of out-of-band data, MSG_TOO_LONG, which indicated that a message was discarded because of the socket buffer limitations. This type of out-of-band data does not exist in the current release, because an X.25 message will not get discarded when it gets too long. "Too long" means that too many packets are received with the M-bit set to 1 and the user has not asked for individual packets with the X25_HEADER ioctl. Instead of getting discarded, the X.25 message will be sent upstream as soon as its length goes over MAXNSDULEN, whether or not the end of the message has been seen (that is, a packet with the M-bit set to 0). MAXNSDULEN is one of the configurable Layer 3 parameters described in Solstice X.25 9.2 Administration Guide.

    If this happens, there are three possible courses of action that may be taken:

    • Increase the socket high water mark using the X25_WR_SBHIWAT ioctl to a maximum of 32767.

    • Request a header on every packet using the X25_HEADER ioctl. This will result in every packet being returned to the user with an extra header byte.

    • Use the X25_RECORD_SIZE ioctl to specify the maximum number of full packets in a complete X.25 message that X.25 should receive before returning the accumulated data to the user as a record.

      Out-of-band messages are serialized in a FIFO (first in, first out) queue, except for interrupt data, which preempts all other out-of-band messages. If the ioctl call X25_OOB_TYPE indicates INT_DATA, then the interrupt packet will be the next packet read on the out-of-band channel, that is, when recv is called with flags set to MSG_OOB. The INT_DATA condition remains true until the out-of-band packet has been read.

      The following piece of code may be used to set up the function func as the signal handler for the SIGURG signal:

      int func();
       (void) signal(SIGURG, func); 

      The signal SIGURG, which indicates an urgent condition present on a socket, may be enabled to indicate an abnormal condition or the arrival of abnormal data at an AF_X25 socket. The signal causes func, the signal handler procedure, to be called. The signal procedure must be called before connect on the calling side and listen on the called side.

      A process receiving the SIGURG signal must examine all potential causes for the signal in order to identify the source of the signal. For example, if a process has multiple AF_X25 sockets open when it receives the SIGURG signal, each open AF_X25 socket will have to be queried with the X25_OOB_TYPE ioctl to determine the signal source. It could well be that the signal did not originate with X.25, but from some other source.

      Upon socket creation, the socket is not associated with a process group ID. If a signal handler routine is used, the user should associate a proper process group ID with the socket as shown below:

      int pgrp, error;
       pgrp = getpid(); /* get the current process id */
       error = ioctl(s, SIOCSPGRP, &pgrp); 

      When a signal handler routine is awakened, pending system calls, for example, recv, accept, connect, select, etc., will be aborted with errno set to EINTR (interrupted system call). The signal handler routine func may be disabled at any time by assigning a default action SIG_DFL to SIGURG:

      (void) signal(SIGURG, SIG_DFL); 

      A more general explanation of signals is in the SunOS 4.x documentation on socket programming.

12.6 Clearing a Virtual Circuit

The close system call is used to discontinue use of a socket and all of the resources held by the socket, as follows:

int s, error;
 error = close(s);

The close call closes the virtual circuit associated with a socket and frees the resources used by the socket. More specifically, close will send a Clear Request packet and then wait for a Clear Confirmation packet if the socket has an active virtual circuit associated with it. An active virtual circuit is one that is either connected, or is in the early stages of connection (that is, Call Request has been sent, but Call Connected has not been received). In this case, if a Clear Confirmation packet is not received after the amount of time specified in the link configuration file, the socket will be closed and close will return. If the socket does not have an active virtual circuit associated with it, close will return immediately.

12.7 Advanced Topics

This section includes material on a variety of advanced topics.

12.7.1 Facility Specification and Negotiation

X.25 user facilities are specified on a per-call basis. The X25_SET_FACILITY ioctl is used to set facilities one at a time. The X25_GET_FACILITY ioctl is used to read facilities one at a time. These ioctl commands support all facilities (1980 and 1984 X.25).

Facilities are set in two places: before issuing a connect call, in order to request desired facilities in the Call Request packet; and before issuing a listen call, in order to negotiate the facilities proposed in an Incoming Call packet.

Facilities are usually read in two places: after a call to connect has succeeded, and after a call to accept has succeeded. This is done to determine the values of the facilities in effect for the resulting connection. Facilities can be read at any time, in general, to determine values which were previously set.

12.7.2 X25_SET_FACILITY/X25_GET_FACILITY ioctls


Note -

The sockets-based interface provides access only to those facilities that were supported in SunNet X.25 7.0. These are a subset of the facilities supported in Solstice X.25 9.2.


The X25_SET_FACILITY ioctl command is used to set the following facilities:

reverse charge   (*)   (#)
 fast select   (*)   (#)
 non-default packet size   (*)
 non-default window size   (*)
 non-default throughput   (*)
 minimum throughput class   (#)
 closed user group   (*)   (#)
 RPOA selection   (*)   (#)
 network transit delay   (#)
 end-to-end transit delay
 network user identification   (#)
 charging information request
 expedited data negotiation
 called AEF
 calling AEF   (#)
 non-X.25 facilities

All of the above facilities can be sent in a Call Request packet. The ones that can be used with a 1980 X.25 interface are marked with an (*), although only the basic forms of the closed user group facility and the RPOA selection can be used in this case. The ones that cannot be sent in a Call Accepted packet are marked with a (#). Solstice X.25 does not permit users to set facilities in Clear Request and Clear Confirm packets.

All of the above facilities can be read using the X25_GET_FACILITY ioctl command. In addition, the following can also be read:

charging information, monetary unit
 charging information, segment
 charging information, call duration
 called line address modified notification
 call redirection notification

Sample programs provided with Solstice X.25 illustrate the use of these facilities. Here, we discuss each of the above facilities in more detail and provide code segments to illustrate their use. For convenience, the variables used in the discussion below are declared here. (Chapter 13, Sockets Programming Example" has a listing of the relevant data structures used by the X25_SET_FACILITY and X25_GET_FACILITY ioctl commands.)

FACILITY    f;   /* facility structure */
 int s;           /* socket */
 int    error;    /* ioctl return value */ 

For brevity, the value returned by ioctl calls is not checked for error.

In the discussion that follows, we show how the user can send facilities in the Call Request packet. In order to send a facility in the Call Accepted packet, the listener should either set the facility before invoking listen, or should set it before causing the Call Accepted packet to be sent (that is, the listener should have used the X25_CALL_ACPT_APPROVAL ioctl command, described later, to cause Solstice X.25 to permit call approval by the user).

The exceptions to this are end-to-end transit delay, expedited data negotiation, Called AEF, and non-X.25 facilities. To send these in the Call Accepted packet, the listener must do call approval, and must set these facilities after accept returns, but before the X25_SEND_CALL_ACPT ioctl command is used to send the Call Accepted packet.

12.7.2.1 Reverse Charge

There are two possible values for this facility: 1 indicates reverse charging, and 0 indicates no reverse charging.

This is set as follows:

u_char    reverse_charge;
 reverse_charge = 1;
 f.type = T_REVERSE_CHARGE;
 f.f_reverse_charge = reverse_charge;
 error = ioctl(s, X25_SET_FACILITY, &f); 

This facility is read as follows:

f.type = T_REVERSE_CHARGE;
 error = ioctl(s, X25_GET_FACILITY, &f);
 reverse_charge = f.f_reverse_charge;

Setting this facility before making the connect call causes this facility to be sent in the Call Request. Setting this facility before making the listen call causes Incoming Calls with the reverse charging facility to be accepted. (Calls that are not reverse-charged are always acceptable.) The listener should read the value of the facility after the accept call returns to find out if the call is reverse-charged.


Note -

Reverse charging must be allowed for this ioctl to work. You allow for reverse charging in the x25tool CUG and Facilities window. To access the CUG and Facilities window, from the x25tool Link Editor window, select CUG and Facilities. Click on Incoming Reverse Charging. See Solstice X.25 9.2 Administration Guide for further details.


12.7.2.2 Fast Select

There are three possible values for this facility. FAST_OFF indicates that fast select is not in effect. FAST_CLR_ONLY indicates fast select with restriction on response, and FAST_ACPT_CLR indicates fast select with no restriction on response.

This is set as follows:

u_char fast_select_type;
 fast_select_type = FAST_CLR_ONLY;
 f.type = T_FAST_SELECT_TYPE;
 f.f_fast_select_type = fast_select_type;
 error = ioctl(s, X25_SET_FACILITY, &f); 

This is read as follows:

f.type = T_FAST_SELECT_TYPE;
 error = ioctl(s, X25_GET_FACILITY, &f);
 fast_select_type = f.f_fast_select_type;

If this facility is set before making the connect call, the Call Request packet is sent out with this facility. If this facility is set before making the listen call, the behavior that follows will depend on whether or not restriction on response was indicated, and on whether the Incoming Call has this facility. In order for an Incoming Call bearing the fast select facility to be acceptable, the listener should have specified fast select (with or without restriction). However, an Incoming Call not bearing the fast select facility will still be acceptable to a listener who has specified fast select with no restriction on response. The type of fast select in effect will be either the type of fast select in the Incoming Call, or fast select with restriction on response if either end of the connection has specified fast select with restriction on response. If the Incoming Call does not specify fast select, and is accepted by a listener who has specified fast select with no restriction on response, fast select will not be in effect for the duration of the call.

A listener that has specified fast select (with or without restriction) must use the X25_SEND_CALL_ACPT ioctl to accept the call or use close to clear the call, after successful completion of the accept call, regardless of whether fast select is in effect for the call. If the type of fast select in effect after accept is either FAST_OFF or FAST_ACPT_CLR, the user may either accept or clear the call. If the type of fast select in effect is FAST_CLR_ONLY, the user cannot accept the call (it can only be cleared). The handling of user data in conjunction with fast select is described later.

12.7.2.3 Packet Size

Packet size is set in the Call Request packet as follows:

u_short sendpktsize, recvpktsize;
 /* set sendpktsize, recvpktsize to desired values */
 f.type = T_PACKET_SIZE;
 f.f_sendpktsize = sendpktsize;
 f.f_recvpktsize = recvpktsize;
 error = ioctl(s, X25_SET_FACILITY, &f);

It is read as follows:

f.type = T_PACKET_SIZE;
 error = ioctl(s, X25_GET_FACILITY, &f);
 sendpktsize = f.f_sendpktsize;
 recvpktsize = f.f_recvpktsize; 

Setting packet size in the Call Request causes the values set to be proposed for the call (a zero value indicates the default for the link). Reading the value after the call is set up yields the result of negotiation.

Packet sizes are set and read in bytes, so that, for example, 128, 256, and 512 are legal values.

12.7.2.4 Window Size

Window size is set in the Call Request packet as follows:

u_short   sendwndsize, recvwndsize;
 /* set sendwndsize, recvwndsize to desired values */
 f.type = T_WINDOW_SIZE;
 f.f_sendwndsize = sendwndsize;
 f.f_recvwndsize = recvwndsize;
 error = ioctl(s, X25_SET_FACILITY, &f);

It is read as follows:

f.type = T_WINDOW_SIZE;
 error = ioctl(s, X25_GET_FACILITY, &f);
 sendwndsize = f.f_sendwndsize;
 recvwndsize = f.f_recvwndsize;

Setting the window size in the Call Request causes the values set to be proposed for the call (a zero value indicates the default for the link). Reading the value after the call is set up yields the result of negotiation.

12.7.2.5 Throughput

Throughput is set in the Call Request packet as follows:

u_char      sendthruput, recvthruput;
 /* set sendthruput, recvthruput to desired values */
 f.type = T_THROUGHPUT;
 f.f_sendthruput = sendthruput;
 f.f_recvthruput = recvthruput;
 error = ioctl(s, X25_SET_FACILITY, &f); 

It is read as follows:

f.type = T_THROUGHPUT;
 error = ioctl(s, X25_GET_FACILITY, &f);
 sendthruput = f.f_sendthruput;
 recvthruput = f.f_recvthruput;

When throughput is set in the Call Request, the values set are proposed for the call (a zero value indicates the default for the link). Reading the value after the call is set up yields the result of negotiation.

12.7.2.6 Minimum Throughput Class

Minimum throughput class is set in the Call Request packet as follows:

u_char      min_sendthruput, min_recvthruput;
 /* set min_sendthruput, min_recvthruput to desired values */
 f.type = T_MIN_THRU_CLASS;
 f.f_min_sendthruput = min_sendthruput;
 f.f_min_recvthruput = min_recvthruput;
 error = ioctl(s, X25_SET_FACILITY, &f); 

It is read as follows:

f.type = T_MIN_THRU_CLASS;
 error = ioctl(s, X25_GET_FACILITY, &f);
 min_sendthruput = f.f_min_sendthruput;
 min_recvthruput = f.f_min_recvthruput; 

This facility may only be set in a Call Request packet, and read from an Incoming Call packet. The receiver of the Incoming Call packet should clear the call (with an appropriate diagnostic) if the proposed minimum throughput values cannot be supported.

12.7.2.7 Closed User Group

The user may set one of three types of Closed User Group facility: CUG_REQ (no outgoing access), CUG_REQ_ACS (with outgoing access), and CUG_BI (bilateral CUG). For CUG_REQ and CUG_REQ_ACS, the CUG is a decimal integer in the range 0-9999 (for 1980 X.25 interfaces, the valid range is 0-99). The extended form of the facility is used for CUG indices in the range 100-9999. This facility is set as follows:

u_short      cug_index;
 /* set cug_index to appropriate value */
 f.type = T_CUG;
 f.f_cug_req = CUG_REQ;    /* could be CUG_REQ_ACS or CUG_BI */
 f.f_cug_index = cug_index;
 error = ioctl(s, X25_SET_FACILITY, &f);
 

To read this facility:

f.type = T_CUG;
 error = ioctl(s, X25_GET_FACILITY, &f);
 cug_req = f.f_cug_req;
 cug_index = f.f_cug_index;

12.7.2.8 RPOA Selection

Solstice X.25 supports the setting of up to three (MAX_RPOA) RPOA transit networks (in the extended form). If only one is specified, the non-extended form of the facility is used. An RPOA transit network is specified as a decimal integer in the range 0-9999.

This facility is set as follows:

u_short      rpoa0, rpoa1, rpoa2;
 /* set rpoa0, rpoa1, rpoa2 */
 f.type = T_RPOA;
 f.f_nrpoa = 3;
 f.f_rpoa_index[0] = rpoa0;
 f.f_rpoa_index[1] = rpoa1;
 f.f_rpoa_index[2] = rpoa2;
 error = ioctl(s, X25_SET_FACILITY, &f);

To read this facility:

f.type = T_RPOA;
 error = ioctl(s, X25_GET_FACILITY, &f);
 rpoa0 = f.f_rpoa_index[0];
 rpoa1 = f.f_rpoa_index[1];
 rpoa2 = f.f_rpoa_index[2];

12.7.2.9 Network Transit Delay

The Transit Delay Selection and Indication facility (TDSAI) is set in the Call Request as follows:

u_short    tr_delay;   /* desired transit delay in milliseconds */
 /* set tr_delay */
 f.type = T_TR_DELAY;
 f.f_tr_delay = tr_delay;
 error = ioctl(s, X25_SET_FACILITY, &f);

This is read as follows:

f.type = T_TR_DELAY;
 error = ioctl(s, X25_GET_FACILITY, &f);
 tr_delay = f.f_tr_delay; 

12.7.2.10 End-to-End Transit Delay

This is set in the Call Request as follows:

u_short   req_delay, desired_delay, max_delay;
 /* set the requested, desired, and maximum delays */
 f.type = T_ETE_TR_DELAY;
 f.f_req_delay = req_delay;
 f.f_desired_delay = desired_delay;
 f.f_max_delay = max_delay;
 error = ioctl(s, X25_SET_FACILITY, &f); 

This is read as follows:

f.type = T_ETE_TR_DELAY;
 error = ioctl(s, X25_GET_FACILITY, &f);
 req_delay = f.f_req_delay;
 desired_delay = f.f_desired_delay;
 max_delay = f.f_max_delay; 

If f_desired_delay is set, f_req_delay must be non-zero; if f_max_delay is set, f_desired_delay must be non-zero. Delay is specified in milliseconds.

12.7.2.11 Network User Identification

This is set as follows (in the example below, NUI is an ASCII string):

char   nui_str[] = "sunhost";
 f.type = T_NUI;
 f.f_nui.nui_len = strlen(nui_str);
 bcopy(nui_str, f.f_nui.nui_data, strlen(nui_str));
 error = ioctl(s, X25_SET_FACILITY, &f); 

Solstice X.25 permits a maximum length of 64 (MAX_NUI) for Network User Identification facility.

To read this facility:

f.type = T_NUI;
 error = ioctl(s, X25_GET_FACILITY, &f);
 nui_str = f.f_nui.nui_data;

12.7.2.12 Charging Information Request

This write-only facility is set as follows:

f.type = T_CHARGE_REQ;
 f.f_charge_req = 1;
 error = ioctl(s, X25_SET_FACILITY, &f);

12.7.2.13 Charging Information

By setting f.type to T_CHARGE_REQ as specified above you make available the following read-only facilities. The facility types are T_CHARGE_MU, T_CHARGE_SEG, and T_CHARGE_DUR. For example, the Charging Information (monetary unit) is read as follows:

typedef struct charge_info_s {
    u_char   charge_len;
 #define MAX_CHARGE_INFO   64
    u_char   charge_data[MAX_CHARGE_INFO];
 } CHARGE_INFO;

 CHARGE_INFO charge_mu;
 f.type = T_CHARGE_MU;
 error = ioctl(s, X25_GET_FACILITY, &f);
 charge_mu = f.f_charge_mu; 

The T_CHARGE_SEG and T_CHARGE_DUR facilities are read in a way similar to the T_CHARGE_MU example above; that is, by using T_CHARGE_SEG or T_CHARGE_DUR for the f.type value, and using f_charge_seg or f_charge_dur in place of f_charge_mu.

The maximum length for the charging information facility permitted by Solstice X.25 is 64 (MAX_CHARGE_INFO). This facility should be read after the call is cleared, but before the socket is closed, since it is received in the Clear Request or Clear Confirm packets.

12.7.2.14 Called Line Address Modified Notification

This is a read-only facility received in either the Call Accepted or Clear Indication packets. It is read as follows:

u_char   line_addr_mod;
 f.type = T_LINE_ADDR_MOD;
 error = ioctl(s, X25_GET_FACILITY, &f);
 line_addr_mod = f.f_line_addr_mod;

12.7.2.15 Call Redirection Notification

This is a read-only facility received in either the Call Accepted or Clear Indication packets. It is read as follows:

typedef struct call_redir_s {
    u_char   cr_reason;
    u_char   cr_hostlen;
    u_char   cr_host[(MAXHOSTADR+1)/2];
 } CALL_REDIR;

 CALL_REDIR call_redir;
 f.type = T_CALL_REDIR;
 error = ioctl(s, X25_GET_FACILITY, &f);
 call_redir = f.f_call_redir; 

12.7.2.16 Expedited Data Negotiation

This facility is set as follows:

u_char expedited = 1;/* 0 indicates non-use of expedited data */
 f.type = T_EXPEDITED;
 f.f_expedited = expedited;
 error = ioctl(s, X25_SET_FACILITY, &f);

It is read as follows:

f.type = T_EXPEDITED;
 error = ioctl(s, X25_GET_FACILITY, &f);
 expedited = f.f_expedited;

12.7.2.17 Called/Calling AEF

There are three types of address extensions: OSI NSAP (AEF_NSAP), Partial OSI (AEF_PARTIAL_NSAP), and Non-OSI (AEF_NON_OSI). The Calling AEF may only be present in the Call Request packet.

Solstice X.25 9.2 Administration Guide describes how Solstice X.25 may be set up to automatically supply the Calling AEF (referred to as address extension) in a Call Request packet.

The Called AEF is set as follows:

typedef struct aef_s {
    u_char   aef_type;
 #define AEF_NONE      0
 #define AEF_NSAP      1
 #define AEF_PARTIAL_NSAP   2
 #define AEF_NON_OSI      3
    u_char   aef_len;
 #define MAX_AEF   40
    u_char   aef[(MAX_AEF+1)/2];
 } AEF;

 AEF   aef;
 aef.aef_type = AEF_NON_OSI;
 aef.aef_len = 7;        /* length in nibbles */
 aef.aef[0] = 0x12;
 aef.aef[1] = 0x34;
 aef.aef[2] = 0x56;
 aef.aef[3] = 0x70;      /* Note, unused nibble is zero */
 f.type = T_CALLED_AEF;
 f_called_aef = aef;
 error = ioctl(s, X25_SET_FACILITY, &f); 

The Called AEF is read as follows:

f.type = T_CALLED_AEF;
 error = ioctl(s, X25_GET_FACILITY, &f);
 aef = f_called_aef;

The Calling AEF is set and read similarly (using T_CALLING_AEF in place of T_CALLED_AEF and f_calling_aef in place of f_called_aef).

12.7.2.18 Non-X.25 Facilities

These are for expert use only. Solstice X.25 permits a maximum of 64 (MAX_PRIVATE) bytes of non-X.25 facilities. These are not looked at by Solstice X.25, but just passed through. Non-X.25 facilities consist of a sequence of facility blocks, where each block begins with a facility marker indicating non-X.25 facilities supported by either the local or remote network, or some arbitrary facility marker. This is set as follows:

typedef struct private_fact_s {
    u_char   p_len;   /* total length of facilities*/
 #define MAX_PRIVATE  64
    u_char   p_fact[MAX_PRIVATE];
       /* facilities exactly as they
        * are present in Call Request or
        * Call Accept packets
        */
 } PRIVATE_FACT;

 PRIVATE_FACT  private;
 /* set the p_len and p_fact fields */
 f.type = T_PRIVATE;
 f.f_private = private;
 error = ioctl(s, X25_SET_FACILITY, &f);

It is read as follows:

f.type = T_PRIVATE;
 error = ioctl(s, X25_GET_FACILITY, &f);
 private = f.f_private;

12.7.2.19 Determining Which Facilities are Present

Since facilities can be read only one at a time, the user needs a way to determine which facilities are present. Solstice X.25 provides the following mechanism for doing this.

The user can read a bit mask that has one bit reserved for each of the facilities described above. This is read as:

u_int     fmask;
 f.type = T_FACILITIES;
 error = ioctl(s, X25_GET_FACILITY, &f);
 fmask = f.f_facilities; 

The following mask bits are defined:

F_REVERSE_CHARGE    /* reverse charging */
 F_FAST_SELECT_TYPE  /* fast select */
 F_PACKET_SIZE       /* packet size */
 F_WINDOW_SIZE       /* window size */
 F_THROUGHPUT        /* throughput */
 F_MIN_THRU_CLASS    /* minimum throughput class */
 F_CUG               /* closed user group selection */
 F_RPOA              /* ROPA transit network */
 F_TR_DELAY          /* network transit delay */
 F_ETE_TR_DELAY      /* end to end transit delay */
 F_NUI               /* network user identification */
 F_CHARGE_REQ        /* charging information request */
 F_CHARGE_MU         /* charging information, monetary unit */
 F_CHARGE_SEG        /* charging information, segment */
 F_CHARGE_DUR        /* charging information, call duration */
 F_LINE_ADDR_MOD     /* called line address modified notification */
 F_CALL_REDIR        /* call redirection notification */
 F_EXPEDITED         /* expedited data negotiation */
 F_CALLED_AEF        /* called AEF */
 F_CALLING_AEF       /* calling AEF */
 F_PRIVATE           /* non-X.25 facilities */ 

For example, to determine if the Call Redirection facility has been received, the following segment of code could be used:

if ((fmask & F_CALL_REDIR) != 0) {
 /*
  * Read its value.
  */
 CALL_REDIR call_redir;
 f.type = T_CALL_REDIR;
 error = ioctl(s, X25_GET_FACILITY, &f);
 call_redir = f.f_call_redir;
 } 

12.7.3 Fast Select User Data

The fast select facility is handled in the following way.

12.7.3.1 Calling Side

To send fast select data, fast_select_type must be set to the proper value (with the X25_SET_FACILITY ioctl) before connect is called (see the section "12.7.1 Facility Specification and Negotiation"of this chapter for more information). Using the CONN_DB structure, a calling DTE can specify a user data field up to 102 bytes (including the optional protocol identifier). If 102 bytes of call user data are not enough for the current fast select message, use the X25_WR_USER_DATA ioctl before calling connect to pass the additional user data. The user data specified in connect will precede this additional user data. To write user data:

typedef struct user_data_db_s {
 u_char      datalen;
 u_char       data[MAX_USER_DATA];
 } USER_DATA_DB;
 int s, error;
 USER_DATA_DB user_data;
 error = ioctl(s, X25_WR_USER_DATA, &user_data)

Here, MAX_USER_DATA is 124.

If connect returns -1 and errno is EFASTDATA, the remote side has cleared the call by sending a Clear Indication packet with up to 32 bytes (1980) or 128 bytes (1984) of user data. At this time, the user can read the user data in the Clear Indication packet with calls to the X25_RD_USER_DATA ioctl until the returned datalen in USER_DATA_DB structure is 0 or less than MAX_USER_DATA, then close the socket with close.

To read user data:

USER_DATA_DB user_data;
 int s, error;
 error = ioctl(s, X25_RD_USER_DATA, &user_data);

If connect returns 0, it indicates that the connection has been set up successfully. If the connection is over an interface that supports 1984 X.25, the remote user may have sent user data in the Call Accepted packet. (This will happen only if the initiator of the connection has specified fast select with no restriction on response.) Thus the initiating user must repeatedly read any user data using the X25_RD_USER_DATA ioctl until the returned length in the USER_DATA_DB structure is less than MAX_USER_DATA.

When a call is cleared after being connected, the Clear Indication packet may contain user data if the interface supports 1984 X.25 and fast select is in effect for that call. Either the initiator of the connection or the responder can send user data in the Clear Request packet. Thus when a call with fast select is cleared by the remote user, user data must be read in the same way as for the other cases.

For 1980 X.25 interfaces, if the connection was accepted by the remote user, the Call Accepted and Clear Request packets will not have any user data; the only time that the Clear Request can have user data is when a fast select call is cleared immediately (this is detectable by means of the EFASTDATA error return).

12.7.3.2 Called Side

To receive a fast select incoming call, the called side must specify either FAST_ACPT_CLR or FAST_CLR_ONLY as the value for fast_select_type using the X25_SET_FACILITY ioctl, before issuing the listen call.

If the Incoming Call has the fast select facility, it will be accepted only if the listener has specified fast select. The incoming call will also be accepted if it does not have the fast select facility and the listener has specified FAST_ACPT_CLR.

The call will be rejected if there are more than 16 bytes of user data, and the called side has either not specified the fast select facility at all, or has specified FAST_OFF (which is equivalent to not specifying fast select).

After accept returns, the called side may use the X25_GET_FACILITY ioctl to determine the type of fast select in effect. For example, if the called side has specified FAST_ACPT_CLR and the calling side has specified FAST_CLR_ONLY, after accept returns, the type of fast select in effect will be FAST_CLR_ONLY. If fast select is indicated, the called side can read the user data that was received in the Call Request by looking at the CONN_DB structure returned by accept. If more than 102 bytes of user data were received, the extra bytes can be read with the X25_RD_USER_DATA ioctl.

The X25_WR_USER_DATA ioctl can be used to specify user data to be sent back in the response to the fast select Call Request. To write more than MAX_USER_DATA bytes of user data, a second X25_WR_USER_DATA ioctl can be used to append the additional data after that from the first X25_WR_USER_DATA ioctl (total length of all user data may not exceed 128 bytes).

If the type of fast select in effect is FAST_CLR_ONLY, the called side can only clear the fast select call by closing the socket (which causes the user data specified by X25_WR_USER_DATA to be sent in the Clear Request). If the type of fast select in effect after accept returns is FAST_ACPT_CLR, the called side has the option, after writing the reply message with the X25_WR_USER_DATA ioctl, of either sending a Clear Request packet with close or sending a Call Accepted packet with the X25_SEND_CALL_ACPT ioctl and thereby entering the normal data transfer state.

int news, error;
 error = ioctl(news, X25_SEND_CALL_ACPT);

When the value in effect is FAST_CLR_ONLY, the called side can only close the socket with the close system call after writing the reply message.

FAST_OFF is the type of fast select that will be in effect when the listener has specified FAST_ACPT_CLR and the incoming call does not have the fast select facility. Even in this case, the listener must use the X25_SEND_CALL_ACPT ioctl to put the connection into normal data transfer state.


Note -

In the current release (and not in SunNet X.25 7.0), the listen socket should not be closed until after the incoming fast select call has been either cleared (with close) or accepted (with X25_SEND_CALL_ACPT).


12.7.4 Permanent Virtual Circuits

Since permanent virtual circuits are always in data transfer state, there is no need to issue a connect on the calling side, or bind, listen, and accept on the called side. Instead, use an ioctl call to bind the socket to a logical channel number and to specify other parameters.

typedef struct pvc_db_s {
 u_short lcn; /* lcn of PVC */
 u_short sendpktsize; /* Maximum packet size */
 u_short recvpktsize; /* Maximum packet size */
 u_char sendwndsize;  /* Output flow control window */
 u_char recvwndsize;  /* Input flow control window */
 } X25_PVC_DB;
 X25_PVC_DB pvc_parms;
 int pvc_so;
 pvc_so = socket(AF_X25, SOCK_STREAM, 0);
 error = ioctl(pvc_so, X25_SETUP_PVC, &pvc_parms); 

In the current release, the sendpktsize, recvpktsize, sendwndsize, and recvwndsize parameters are ignored. The default value in the link configuration file is always used. By default, the lowest numbered WAN link is used for the permanent virtual circuit. If you desire some other link for the permanent virtual circuit, you must select the desired link using the X25_SET_LINK ioctl as described earlier, after the socket call, but before the X25_SETUP_PVC ioctl. Permanent virtual circuits are not supported over LAN interfaces.

12.7.5 Call Acceptance by User

Normally Incoming Call packets are examined and responded to by X.25. If the call is accepted, a Call Accepted packet is sent by X.25 directly. In the event a user process wants to have additional checks before sending a Call Accepted packet, an X25_CALL_ACPT_APPROVAL ioctl may be used.

int approved_by_user, s, error;
 error = ioctl(s, X25_CALL_ACPT_APPROVAL, &approved_by_user);

where approved_by_user = 0 means the approval is done by X.25, and approved_by_user = 1 means approval is done by the user process. By default (that is, if this call is not issued), approval is done by X.25. Note that if a user wants to do call approval, the X25_CALL_ACPT_APPROVAL ioctl must be issued before the listen call is issued.

Regardless of the value of approved_by_user, X.25 always performs address matching and facilities negotiation before notifying accept. If a user process assumes the final incoming call approval, accept will return without sending a Call Accepted packet. At this time, the user process should reply as soon as possible to avoid the Call Request timeout on the remote calling side. To accept the call, use:

int news, error;
 error = ioctl(news, X25_SEND_CALL_ACPT);

Here, news is the socket descriptor returned by accept.

The X25_SEND_CALL_ACPT ioctl call is also needed for fast select calls, as described in an earlier section. To reject the call, simply close the socket:

int news;
 close(news); 

where news is the socket descriptor returned by accept.

12.7.6 Accessing the Link (X.25) Address

The X.25 client can set the local link X.121 (X.25) address through an X.25 socket owned by the superuser. (The default value is established in the Interface Configuration window in x25tool, as described in Solstice X.25 9.2 Administration Guide):

typedef struct link_adr_s {
    int      linkid;  /* id of link */
    u_char   hostlen; /* length of BCDs */
    u_char   host[(MAXHOSTADR+1)/2];
 } LINK_ADR;
 LINK_ADR addr;
 int so, error;
 error = ioctl(so, X25_WR_LINKADR, &addr);

Set linkid to the identifier of the desired link.

The local link X.121 address can be read at any time with:

LINK_ADR addr;
 int s;
 error = ioctl(s, X25_RD_LINKADR, &addr);

The returned addr is actually the link address specified in x25tool (for the link specified in the linkid field of the LINK_ADR structure) unless a new address has been assigned to the link.

The X25_WR_LINKADR ioctl can be used to assign new X.25 addresses to a link.

12.7.7 Accessing High Water Marks of Socket

The AF_X25 socket provides a flow control mechanism using high and low water marks on both the send and receive sides of an X.25 virtual circuit. When the amount of queued data goes above the high water mark, additional data is blocked until the queued data falls below the low water mark. Blocking received data is accomplished by not acknowledging receipt of packets until the user reads the data. Blocking send data is accomplished by blocking the user process invoking send or write.

The default high water mark for both sending and receiving is 2048 bytes. The low water mark is always set to half the high water mark. Note that the high water mark is only an approximation of the maximum amount of data allowed to be queued up.

A user process may set or read the high water mark as described below. To read:

typedef struct so_hiwat_db_s {
    short   sendhiwat;
    short   recvhiwat;
 } SO_HIWAT_DB;
 SO_HIWAT_DB hiwater;
 int s, error;
 error = ioctl(s, X25_RD_SBHIWAT, &hiwater);

To write:

error = ioctl(s, X25_WR_SBHIWAT, &hiwater);

12.7.8 Accessing the Diagnostic Code

The user may read the cause or diagnostic code in a Clear Indication or Reset Indication packet received from the remote end. The user may also write the cause or diagnostic code in Clear Request and Reset Request packets to be transmitted to the remote end.

typedef struct x25_cause_diag_s {
 u_char   flags;
 #   define RECV_DIAG    0
 #   define DIAG_TYPE   1
 #   define WAIT_CONFIRMATION 2
 /*  bit 0 (RECV_DIAG)=
  *  0: no cause and diagnostic codes
  *  1: receive cause and diagnostic codes.
  *  bit 1 (DIAG_TYPE)=
  *  0: reset cause and diagnostic codes in data array
  *  1: clear cause and diagnostic codes in data array
  *  bit 2 (WAIT_CONFIRMATION)=
  *  0: no wait after X25_WR_DIAG_CODE ioctl
  *  1: wait returned cause and diagnostic codes after
  *  X25_WR_DIAG_CODE ioctl.
  */
    u_char   datalen; /* byte count of data array */
    u_char   data[64];
 } X25_CAUSE_DIAG;
 X25_CAUSE_DIAG diag;
 int s, error; 

To read:

error = ioctl(s, X25_RD_CAUSE_DIAG, &diag);

To write:

error = ioctl(s, X25_WR_CAUSE_DIAG, &diag);

The data field in X25_CAUSE_DIAG contains the cause and diagnostic code.

Upon receiving a Clear Indication or Reset Indication packet, the X25_RD_CAUSE_DIAG ioctl may be issued to determine the cause and diagnostic associated with the packet. The datalen field contains the length in bytes of the information in data. When reading the diagnostic, if bit RECV_DIAG (that is, bit 0) is set, it indicates that the information in data is valid. If bit DIAG_TYPE (that is, bit 1) is set, it indicates that the diagnostic was received in a Clear Indication; otherwise, it was received in a Reset Indication.

The X25_WR_CAUSE_DIAG ioctl enables the user to send a Clear Request or Reset Request packet with the desired cause and diagnostic codes. If the user supplies only one byte in the data field, X.25 will use the cause code DTE_ORIGINATED, and use the provided byte as the diagnostic.

The X25_WR_CAUSE_DIAG ioctl call will send a Clear Request or Reset Request. To send a Clear Request, set bit DIAG_TYPE (that is, bit 1) in flags:

X25_CAUSE_DIAG diag;
 int s, error;
 diag.flags = 1 << DIAG_TYPE; /* Clear Request */
 diag.datalen = 2;
 diag.data[0] = 0;
 diag.data[1] = 67;
 error = ioctl(s, X25_WR_CAUSE_DIAG, &diag);

To send a Clear Request and wait for confirmation, set bit WAIT_CONFIRMATION (that is, bit 2) in flags:

X25_CAUSE_DIAG diag;
 int s, error;
 diag.flags = (1 << DIAG_TYPE) | (1 << WAIT_CONFIRMATION);
 diag.datalen = 2;
 diag.data[0] = 0;
 diag.data[1] = 67;
 error = ioctl(s, X25_WR_CAUSE_DIAG, &diag);

To send a Reset Request and wait for confirmation:

X25_CAUSE_DIAG diag;
 int s, error;
 diag.flags = 1 << WAIT_CONFIRMATION;
 diag.datalen = 2;
 diag.data[0] = 0;
 diag.data[1] = 0;   /* can be any valid diagnostic */
 error = ioctl(s, X25_WR_CAUSE_DIAG, &diag);

A close is still necessary to free all resources held by this socket and the associated virtual circuit after a Clear Indication or Clear Confirmation packet is received. After the DTE receives a Clear Indication packet, recv will return zero bytes after all unread data has been read. Calling send after the Clear Indication packet is received will not return an error. Note that this behavior is different from that of SunNet X.25 7.0, in which send does return an error.

To be notified when a Clear Indication packet is received, so that you can use the X25_RD_CAUSE_DIAG ioctl, you can use the following mechanism: Enable a third type of out-of-band data (see "12.5.4 Out-of-Band Data ") and receive the SIGURG signal when this type of out-of-band data arrives. To enable the signalling of Clear Indication packets, use the following ioctl:

error = ioctl(s, X25_OOB_ON_CLEAR, 0);

This will enable the reception of the following type of out-of-band data, which can be read with the X25_OOB_TYPE ioctl:

#define VC_CLEARED 31  /* virtual circuit cleared */

See "12.5.4 Out-of-Band Data " for a complete description of how to handle out-of-band data.


Note -

If an X25_WR_CAUSE_DIAG ioctl is not issued before close, X.25 fills an appropriate cause and diagnostic code in any Clear Request packet sent as a result (this will not happen if the connection is inactive at the time the call is issued).


12.8 Routing ioctls

In this section, we describe the ioctls used to manage the Solstice X.25 routing function in the sockets-based interface. The Solstice X.25 routing function is described in detail in Solstice X.25 9.2 Administration Guide. The data structure used for routing is as follows:

typedef struct x25_route_s { 
   uint32_t   index;  
   u_char    r_type;
 #define     R_NONE        0
 #define     R_X121_HOST   1
 #define     R_X121_PREFIX 2
 #define     R_AEF_HOST    3
 #define     R_AEF_PREFIX  4
   CONN_ADR  x121;  
   u_char    pid_len;
 #define     MAX_PID_LEN   4  
   u_char    pid[MAX_PID_LEN];  
   AEF       aef;  
   int       linkid;  
   X25_MACADDR  mac;  
   int       use_count;  
   char      reserved[16];
 } X25_ROUTE;

The following declarations will be used in the code segments used for illustration:

int s, error;
 X25_ROUTE r;

To add a route, set the fields in the X25_ROUTE structure to desired values, and execute the X25_ADD_ROUTE ioctl as follows:

error = ioctl(s, X25_ADD_ROUTE, &r);

To obtain the routing information for a given destination address, set the destination address in the X25_ROUTE structure and execute the X25_GET_ROUTE ioctl:

error = ioctl(s, X25_GET_ROUTE, &r);

To remove a route for a given destination address, set the destination address in the X25_ROUTE structure and execute the X25_RM_ROUTE ioctl:

error = ioctl(s, N_X25_RM_ROUTE, &r);

To flush all routes out, execute the X25_FLUSH_ROUTES ioctl:

error = ioctl(s, X25_FLUSH_ROUTES);

The following code segment illustrates how one may cycle through all the routes configured in the system and obtain the parameters for each of them:

r.index = 0;
 do {  
     error = ioctl(s, X25_GET_NEXT_ROUTE, &r);  
     if (error == 0)    
         /* print the route */;
 while (error == 0);

When there are no routes left, error will be -1, and errno will be set to ENOENT.

The X25_ADD_ROUTE, X25_RM_ROUTE, and X25_FLUSH_ROUTES ioctls require superuser privilege; X25_GET_ROUTE and X25_GET_NEXT_ROUTE do not.

12.9 Miscellaneous ioctls

This section describes some miscellaneous ioctl calls that were either not covered in the previous sections, or are supported from previous releases for backward compatibility. This does not imply backward compatibility with all user-written software for previous releases of Solstice X.25.

12.9.1 Obtaining Statistics

Use the X25_GET_NLINKS ioctl to determine the number of links configured:

int s, error, nlinks;
 error = ioctl(s, X25_GET_NLINKS, &nlinks);

The X.25 software maintains statistics for levels 1, 2, and 3. The statistics are made available for any socket at any time (that is, the sockets over which the calls for reading statistics are issued need not have superuser privilege).

The X25_RD_LINK_STATISTICS ioctl is used to read statistics of levels 1 and 2:

struct ss_dstats {
    int32_t ssd_ipack;    /* input packets */
    int32_t ssd_opack;    /* output packets */
    int32_t ssd_ichar;    /* input bytes */
    int32_t ssd_ochar;    /* output bytes */
 };
 
 /* error stats */
 struct ss_estats {
    int32_t sse_abort;      /* abort received */
    int32_t sse_crc;        /* CRC error */
    int32_t sse_overrun;    /* receiver overrun */
    int32_t sse_underrun;   /* xmitter underrun */
 };
 typedef struct x25_link_stat_db_s {
    int linkid; /* link identifier */
    u_short state;
    /* 0: initial state
     * 1: SABM outstanding
     * 2: FRMR outstanding
     * 3: DISC outstanding
     * 4: information transfer state
     */
    u_short hs_sentsabms;       /* sabms sent */
    struct ss_dstats hs_data;   /* data stats */
    struct ss_estats hs_errors; /* error stats */
 } X25_LINK_STAT_DB;

 X25_LINK_STAT_DB link_stats;
 int s, error;
 error = ioctl(s, X25_RD_LINK_STATISTICS, &link_stats); 

The linkid field in the X25_LINK_STAT_DB structure identifies the interface whose statistics are to be read.

The X25_RD_PKT_STATISTICS ioctl is used for reading packet-level statistics for a specified logical channel:

typedef struct x25_pkt_stat_db_s {
    int linkid;   /* link identifier */
    u_short lcn;  /* logical channel identifier */
    u_char state; /* level 3 lcn state */
 /* current state of virtual circuit
    ST_OFF (0): virtual circuit not active
    ST_LISTEN (1): passive wait for incoming call
    ST_READY (2): connection in process of being established
                  (connection NOT up yet)
    ST_SENT_CALL (3): wait for call connected packet
    ST_RECV_CALL (4): wait user to send call accepted packet
    ST_CALL_COLLISION (5): call collision state
    ST_RECV_CLR (6): unused (should indicate reception of a
                 clear packet)
    ST_SENT_CLR (7): wait for clear confirmation packet
    ST_DATA_TRANSFER (8): in normal data transfer
    ST_SENT_RES (9): wait reset confirmation packet
 */
    u_char sub_state; /* level 3 lcn sub_state */
    /* valid only when state is ST_DATA_TRANSFER
       bit 0 (RECV_RNR): remote busy
       bit 1 (RECV_INT): wait user to read interrupt data
       bit 2 (SENT_INT): wait for interrupt confirmation
       bit 3 (SENT_RNR): local busy
    */
    u_char intcnt;   /* number of received interrupt datum */
    u_char resetcnt; /* times of virtual circuit reset */
    int sendpkts;    /* number of output packets */
    int recvpkts;    /* number of input packets */
    short pgrp;      /* process group of socket, if not 0 */
    short flags;     /* flag bits. If bit 0 is set, it */
    /* indicates an incoming call. */
    /* Otherwise, it is an outgoing call. */
 } X25_PKT_STAT_DB;

 X25_PKT_STAT_DB pkt_stats;
 int s, error;
 error = ioctl(s, X25_RD_PKT_STATISTICS, &pkt_stats); 

The linkid field in the X25_PKT_STAT_DB structure identifies a link, and lcn identifies the logical channel whose statistics are to be read. Note that pkt_stats.lcn needs to be set to the proper logical channel number before making the X25_RD_PKT_STATISTICS ioctl call.

Solstice X.25 also provides ioctl commands to read the status of all of the links currently active and all the virtual circuits currently active. Use the X25_GET_NEXT_LINK_STAT ioctl to obtain link status as follows:

/*  The following is used to cycle through all the interfaces -
  *  static HDLC links as well as links used for LLC2.
  */
 typedef struct x25_next_link_stat_s {
    u_char opt;             /* search option */
 #define  GET_FIRST  0      /* get first one */
 #define  GET_NEXT   1      /* get next one */
    u_char specific;        /* applies to specified interface */
    u_char link_type;       /* HDLC_TYPE, LLC_TYPE */
    int linkid;             /* interface id */
    X25_MACADDR mac;        /* always null in current release */
 /* Level 2 states */
 #define LINKSTATE_DOWN  0               /* initial state */
 #define LINKSTATE_SABM  1               /* SABM outstanding */
 #define LINKSTATE_FRMR  2               /* FRMR outstanding */
 #define LINKSTATE_DISC  3               /* DISC outstanding */
 #define LINKSTATE_UP    4               /* info transfer state */
    u_short state;              /* link state--see preceding defines */
    u_short hs_sentsabms;       /* sabms sent */
    struct ss_dstats hs_data;   /* data stats */
    struct ss_estats hs_errors; /* error stats */
 } X25_NEXT_LINK_STAT;

    int s;
    int error;
    X25_NEXT_LINK_STAT lstats;

    lstats.opt = GET_FIRST;
    lstats.specific = 0;
    do {
       error = ioctl(s, X25_GET_NEXT_LINK_STAT, &lstats);
       if (error == 0)
       /* print the statistics */;
       } while (error == 0); 

If the statistics for a specific link are required, set specific to 1, and linkid to the id of the interface whose statistics are required. After the first call, the opt field will automatically be changed to GET_NEXT. When the statistics for all the links are returned, error will be -1, and errno will be set to ENOENT.

Use the X25_GET_NEXT_VC_STAT ioctl to obtain the status of all the virtual circuits as follows:


Example 12-1 Reading Virtual Circuit Status

/* X25_NEXT_VC_STAT is used to cycle through all virtual circuits,
  * over HDLC as well as LLC type links.
  */
 typedef struct x25_next_vc_stat_s {
    u_char    opt;       /* search option */
    u_char    specific;  /* applies to specified linkid */
    u_char    link_type; /* HDLC_TYPE, LLC_TYPE */
    int    linkid;       /* link id */
    u_short    lcn;      /* logical channel to return */
    u_char    state;     /* level 3 lcn state */
 #define ST_OFF             0
 #define ST_LISTEN          1
 #define ST_READY           2
 #define ST_SENT_CALL       3
 #define ST_RECV_CALL       4
 #define ST_CALL_COLLISION  5
 #define ST_RECV_CLR        6
 #define ST_SENT_CLR        7
 #define ST_DATA_TRANSFER   8
 #define ST_SENT_RES        9
    u_char    sub_state;  /* level 3 lcn sub_state */
 #define RECV_RNR  0
 #define RECV_INT  1
 #define SENT_INT  2
 #define SENT_RNR  3
    u_char    intcnt;    /* number of received interrupts */
    u_char    resetcnt;  /* times of virtual circuit reset */
    int    sendpkts;     /* number of output packets */
    int    recvpkts;     /* number of input packets */
    short    pgrp;       /* process group, if any */
    short    flags;      /* various flags for future */
 #define  INCOMING_CALL  0x01
 #define  IS_A_PVC  0x02
    struct sockaddr  sa;  /* Remote X.121/IP address */
    AEF    aef;           /* Remote AEF, if any */
    X25_MACADDR  mac;    /* Remote mac for LLC links */
 } X25_NEXT_VC_STAT;

    int s;
    int error;
    X25_NEXT_VC_STAT vstats;

    vstats.opt = GET_FIRST;
    vstats.specific = 0;
    do {
       error = ioctl(s, X25_GET_NEXT_VC_STAT, &vstats);
       if (error == 0)
       /* print the statistics */;
       } while (error == 0);

hoIf the statistics of virtual circuits for a specific link are required, set specific to 1, and linkid to the id of the desired interface. After the first call, the opt field will automatically be changed to GET_NEXT. When the statistics for all the virtual circuits are returned, error will be -1, and errno will be set to ENOENT.

12.9.1.1 Obtaining Version Number

The X25_VERSION ioctl returns the version number of the Solstice X.25 kernel code. You can issue this call on any socket. The version number returned for the current release of Solstice X.25 is 92.

int so, version, error;
 error = ioctl(s, X25_VERSION, &version);