Solstice X.25 9.2 Developer's Guide

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.