Thursday, May 15, 2008

Speaking UNIX: Inside TCP/IP

The Internet has played a huge role in the advancement of technology, business, and everyday life for huge numbers of the world's people. Configuring a computer to communicate over a network and connecting to the Internet has become an essential task for administrators. This article shows how to configure a server running IBM® AIX® to connect to and use the Internet.

As defined in Wikipedia, the Internet is a worldwide, publicly accessible series of interconnected computer networks that transmit data by packet switching using the standard Internet Protocol (IP). The Internet is, in a sense, the largest network in the world and spans the globe many times over.

The Internet and the Web are not the same thing, although people interchange the terms frequently. The World Wide Web (WWW), or the Web, is a collection of hypertext documents containing images, audio and video clips, and other files interlinked and accessed over the Internet.

A simplistic example of the two is when you connect to your Internet Service Provider (ISP) using a cable modem. Connecting to your ISP using a cable does exactly that: It connects you to your ISP's network and to the Internet, but you are still not using the Web—not until you open a Web browser, such as Mozilla Firefox or Apple Safari, and connect to a Web site.

Configuring TCP/IP and the network adapter

For a server running the AIX operating system to communicate over a network and connect to the Internet, you must configure the network adapter or, depending on the system, edit multiple files to set up TCP/IP. However, IBM has made this task easy with one simple switch inside the System Management Interface Tool (SMIT).

Before beginning to configure the network adapter, first document the following information:

  • IP address to assign to the network adapter
  • Host name of the target server
  • Name of the domain of which the target server is a member
  • Subnet mask
  • Name servers
  • Gateway address

To configure a network adapter on a server running AIX, perform the following steps:

  1. Log in to the system as root or su – to root.
  2. Start the SMIT program, and then choose Communications Applications and Services > TCP/IP > Minimum Configuration & Startup.

    Tip: SMIT has many shortcuts, or fast paths, to allow you to quickly get to the menu or task needed. In this case, simply typing smitty mktcpip bypasses having to navigate through the three previous menus.

  3. Select the network adapter you want to configure from the list shown in Figure 1, and then click Enter. For this example, en2 is used.

    Figure 1. Available network adapters
    Available network adapters

     

    After you select the desired network adapter, a new window is built that displays all the settings you need to configure the network adapter.

  4. Taking the information you documented earlier, type the host name, IP address, subnet mask, domain name, name server IP address, and default gateway address. If you want the network adapter to start as soon as you've made your changes, change START Now to Yes, as shown in Figure 2.

    Figure 2. Minimum configuration settings for the network adapter
    Minimum configuration settings

     
  5. Verify the information you typed, and then click Enter.

    AIX makes the changes requested and starts the TCP/IP daemons (if they haven't already been started). In Figure 3, note that the TCP/IP daemons were already running, as en0 and en1 are configured on this AIX system.



    Figure 3. Network adapter changes in progress
    Changes in progress

     
  6. Exit SMIT by clicking either F10 or Esc + 0 (zero).

 

DNS

A Domain Name System (DNS) server interprets IP addresses into domain names and locations of other computers or Web sites. Without DNS, you would need to enter the IP address into a Web browser. For example, if you didn't have access to DNS and wanted to view IBM's Web site, you would have to type 129.42.18.103 instead of www.ibm.com. DNS eases the use of Web browsing over the Internet as well as connecting to other servers over a network. It's much easier to remember www.ibm.com than 129.42.18.103!

Another advantage to using DNS is that from time to time, IP addresses change on servers. For instance, a server may need to move from one location to another, or a server may be replaced with new equipment. Performing such moves sometimes requires changing the IP address on the server after it reaches its new home because of a different network scheme at the new location. When this happens, it's much easier on users to remember the name of the server instead of having to remember what the old and new IP address are. If the server move was successful, users will never know the difference.

As mentioned earlier, when setting up the network adapter, you typed the IP address location to a name server. This server is your primary DNS server location. It is wise to have several DNS servers to rely on in case one should fail during an address lookup. If multiple DNS servers are used, when the server is looking up an IP or host name to cross-reference, if the first DNS server doesn't have the information or is unavailable, the lookup request will move to the second DNS server, and so on.

To add other DNS servers, you must modify the /etc/resolv.conf file. Listing 1 provides an example of such a file.


Listing 1. An /etc/resolv.conf file
 
                
domain  ATC-DOMAIN.com

nameserver      10.20.30.23
nameserver      10.20.30.24
nameserver 10.20.30.25

search  atc-domain2.com, atc-domain3.com, atc-domain4.com

options debug

 

The sections that follow provide descriptions of each parameter used in Listing 1.

domain

The domain parameter instructs the resolving function to append <domain name> to the end of the string to lookup if the string does not end with a . (period). For example, if the string entered for lookup is ibm, the actual string that will be used is ibm.ATC-DOMAIN.com.

If no domain is included in the domain parameter (that is, using ATC-AIX1 rather than ATC-AIX.ATC-DOMAIN.com), the current server's root domain is assumed.

Note: Only one domain entry can be used in the /etc/resolv.conf file.

nameserver

The nameserver parameter tells the server which DNS server to resolve IP addresses and host names against. The resolver queries each name server in the order provided in /etc/resolv.conf until the IP address has been properly resolved.

Note: Only three name server entries can be used in the /etc/resolv.conf file.

search

The search parameter provides a list of domains to the resolver to use when resolving an IP address or host name. Only the one domain or search entry can be used. If domain is used, search will be the value of domain.

Note: Although you can add several domain names to the search option, there is a limit of 1,024 characters.

Options

The options parameter provides an extra means of debugging and adjusts the lookup function to your liking:

  • debug: This option turns on debugging for the resolving function
  • ndots:<N>: If a domain with <N> or more periods is found, DNS attempts to resolve the string first without appending the search domain list.

    For more information on DNS, see Resources.



 

Testing the Internet connection

Now that you've configured the network adapter and modified /etc/resolv.conf to your liking, you can test your Internet connection. There are many ways to test your connection, so I cover only a couple of the basic, but useful, troubleshooting tools.

The ping command

One of the easiest ways to verify that you've configured your network adapter correctly and can communicate with the Internet is to ping an IP address. The ping command is a tool for testing whether the target is reachable by your server and its network. Basically, ping sends Internet Control Message Protocol (ICMP) packets from your server to the destination server, and then receives a response from the destination server. If the response is received, you have connectivity to the destination server. Using ping is a simple and quick way to determine if there is a problem, how fast data is being sent between servers, and if you have connectivity at all.

The following example confirms that I have connectivity to Google.com's IP address, 64.233.167.99:

                ping 64.233.167.99
PING 64.233.167.99: (64.233.167.99): 56 data bytes
64 bytes from 64.233.167.99: icmp_seq=0 ttl=240 time=40 ms
64 bytes from 64.233.167.99: icmp_seq=1 ttl=240 time=41 ms
64 bytes from 64.233.167.99: icmp_seq=2 ttl=240 time=48 ms
64 bytes from 64.233.167.99: icmp_seq=3 ttl=240 time=40 ms
^C
----64.233.167.99 PING Statistics----
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 40/42/48 ms

 

Sometimes, however, ping can't be the only tool used to confirm connectivity to a server, because a server may have ICMP requests blocked by firewalls. The following example shows ICMP being blocked and simulating no connectivity to IBM.com's IP address, 129.42.18.103:

                ping 129.42.18.103
PING 129.42.18.103: (129.42.18.103): 56 data bytes
^C
----129.42.18.103 PING Statistics----
6 packets transmitted, 0 packets received, 100% packet loss

 

So far, I've only attempted to ping IP addresses. After you've confirmed this first troubleshooting step, it's also a good test to attempt to ping the actual host name that is resolved in DNS:

                ping google.com
PING google.com: (64.233.167.99): 56 data bytes
64 bytes from 64.233.167.99: icmp_seq=0 ttl=240 time=40 ms
64 bytes from 64.233.167.99: icmp_seq=1 ttl=240 time=43 ms
^C
----google.com PING Statistics----
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 40/41/43 ms

 

The traceroute command

Although ping may have failed on one attempt, this doesn't necessarily mean that you don't have connectivity to the Internet. The following example shows that ping failed when trying to communicate with ATC-AIX2:

                ping ATC-AIX2
PING ATC-AIX2: (10.20.90.41): 56 data bytes
^C
----10.20.90.41 PING Statistics----
6 packets transmitted, 0 packets received, 100% packet loss

 

Here's the same test using the traceroute command:

                traceroute ATC-AIX2

traceroute to ATC-AIX2.ATC-DOMAIN.com (10.20.90.41) from ATC-AIX1.ATC-DOMAIN.com 
    (10.20.30.40), 30 hops max
outgoing MTU = 1500
 1  10.20.30.254 (10.20.30.254)  8 ms  3 ms  3 ms
 2  10.20.30.252 (10.20.30.252)  4 ms  4 ms  3 ms
 3  19.16.15.240 (19.16.15.240)  5 ms  5 ms  5 ms
 4  17.30.11.23 (17.30.11.23)  4 ms  5 ms  4 ms
 5  10.20.90.252 (10.20.90.252)  4 ms  5 ms  4 ms
 6  10.20.90.252 (10.20.90.254)  8 ms  5 ms  4 ms
 7  10.20.90.41 (10.20.90.41) 8 ms  6 ms  5 ms

 

The traceroute command can be a helpful troubleshooting tool. If your traceroute results in failure, the output can lead you in the right direction—namely, which server or network equipment may be blocking your access.

The nslookup and dig commands

With the ping and traceroute commands, notice that host names were primarily used. Using host names is helpful for users, because they don't need to memorize difficult IP addresses. One method to determine whether DNS is in fact working is to use the name server lookup, or nslookup, command. Using nslookup can provide host name information as well as IP addresses associated with the host name. This command is useful if users report an issue when they try to connect to a server but don't get a response. In such a situation, it could be that their DNS information isn't updated and old addresses are being used, which you can use nslookup to verify quickly.

The following code displays the IP addresses associated with IBM.com:

                nslookup ibm.com
Server:  ATC-AIX1.ATC-DOMAIN.com
Address:  10.20.30.40

Non-authoritative answer:
Name:    ibm.com
Addresses:  129.42.17.103, 129.42.18.103, 129.42.16.103

 

A newer program similar to nslookup is dig. The dig command provides the same information as nslookup but with a fuller view of how DNS is set up for the target:

                dig ibm.com

; <<>> DiG 9.2.0 <<>> ibm.com
;; global options:  printcmd
;; Got answer:
;; -<<HEADER<<- opcode: QUERY, status: NOERROR, id: 16463
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 13, ADDITIONAL: 13

;; QUESTION SECTION:
;ibm.com.                       IN      A

;; ANSWER SECTION:
ibm.com.                14740   IN      A       129.42.16.103
ibm.com.                14740   IN      A       129.42.17.103
ibm.com.                14740   IN      A       129.42.18.103

;; AUTHORITY SECTION:
.                       24402   IN      NS      I.ROOT-SERVERS.NET.
.                       24402   IN      NS      G.ROOT-SERVERS.NET.
.                       24402   IN      NS      C.ROOT-SERVERS.NET.
.                       24402   IN      NS      F.ROOT-SERVERS.NET.
.                       24402   IN      NS      M.ROOT-SERVERS.NET.
.                       24402   IN      NS      E.ROOT-SERVERS.NET.
.                       24402   IN      NS      L.ROOT-SERVERS.NET.
.                       24402   IN      NS      D.ROOT-SERVERS.NET.
.                       24402   IN      NS      J.ROOT-SERVERS.NET.
.                       24402   IN      NS      H.ROOT-SERVERS.NET.
.                       24402   IN      NS      A.ROOT-SERVERS.NET.
.                       24402   IN      NS      B.ROOT-SERVERS.NET.
.                       24402   IN      NS      K.ROOT-SERVERS.NET.

;; ADDITIONAL SECTION:
I.ROOT-SERVERS.NET.     31808   IN      A       192.36.148.17
G.ROOT-SERVERS.NET.     2961    IN      A       192.112.36.4
C.ROOT-SERVERS.NET.     36288   IN      A       192.33.4.12
F.ROOT-SERVERS.NET.     40867   IN      A       192.5.5.241
M.ROOT-SERVERS.NET.     15357   IN      A       202.12.27.33
E.ROOT-SERVERS.NET.     26901   IN      A       192.203.230.10
L.ROOT-SERVERS.NET.     21568   IN      A       199.7.83.42
D.ROOT-SERVERS.NET.     9464    IN      A       128.8.10.90
J.ROOT-SERVERS.NET.     35190   IN      A       192.58.128.30
H.ROOT-SERVERS.NET.     7936    IN      A       128.63.2.53
A.ROOT-SERVERS.NET.     35190   IN      A       198.41.0.4
B.ROOT-SERVERS.NET.     29770   IN      A       192.228.79.201
K.ROOT-SERVERS.NET.     16473   IN      A       193.0.14.129

;; Query time: 3 msec
;; SERVER: 10.20.30.40#53(10.20.30.40)
;; WHEN: Wed Mar 12 17:02:32 2008
;; MSG SIZE  rcvd: 492

 

Connect to the Web

After successfully testing the Internet connection and verifying that DNS is set up correctly by using the ping, traceroute, and nslookup commands, you're ready to get on the Web. Simply open your preferred Web browser, type the Uniform Resource Locator (URL) you want to view (see Figure 4), and viola! Congratulations: You're on the Internet and viewing the Web!


Figure 4. Connecting to the Web
Connecting to the Web


 

Conclusion

Connecting to the Internet and viewing Web sites is easy in AIX. IBM has made configuration of AIX and network adapters easy. Simply configure your network adapter, direct DNS to a valid DNS server, and you'll be surfing the Web in no time! Enjoy!

Tuesday, April 15, 2008

Openssh with AIX chroot

Sometimes you might want to restrict users to specific directories so that they are not able to look into the whole system. This can be achieved by creating the chroot users. This article describes how to set up an IBM® AIX® chroot environment and use it with ssh, sftp, and scp. You will also learn about the prerequisites for AIX and openssh, and how to configure and use a chroot environment. A downloadable sample shell script that automatically sets up this environment is also provided.

Introduction

IBM-supported versions of OpenSSH (openssh-4.5 onwards) has included the chroot feature. It looks for "." (dot) in the user's home directory and then the chroot () call changes the root directory of the user so that the directory before "." (dot) becomes the chrooted directory. This article helps you set up a chroot environment on AIX and use it with ssh, sftp, and scp.

We assume that the reader has basic AIX skills, so we do not explain general AIX tasks in detail like updating AIX, making logical volume, and the like. We concentrate on setting up the chroot environment and using it with IBM-supported openssh.

Prerequisites

The chroot feature is supported in the OpenSSH-4.5p1 version onwards. The filesets to be downloaded from sourceforge.net are openssh-4.5.0.5302(OpenSSH-4.5p2-r2) and later. AIX 5.3 (at minimum TL06 is required) and AIX 6.1 or higher are supported.

Openssh-4.5p1(openssh-4.5.0.5200) for AIX 5.2 also supports the chroot feature. The minimum AIX release is AIX 5200-10.

Download the latest level of openssh from SourceForge.net, and the latest openssl installp filesets from the IBM; see the Resources section for these downloads.

You need to register to this site for downloading the filesets.

Once you have all the filesets, you can install them with smitty install or using AIX NIM.

These are the filesets that get installed for openssh 4.5.0.5302 and openssl 0.9.8.4:

/home/chroot # lslpp -l | grep open
 openssh.base.client     4.5.0.5302  COMMITTED  Open Secure Shell Commands
 openssh.base.server     4.5.0.5302  COMMITTED  Open Secure Shell Server
 openssh.license         4.5.0.5302  COMMITTED  Open Secure Shell License
 openssh.man.en_US       4.5.0.5302  COMMITTED  Open Secure Shell
 openssh.msg.en_US       4.5.0.5302  COMMITTED  Open Secure Shell Messages -
 openssl.base               0.9.8.4  COMMITTED  Open Secure Socket Layer
 openssl.license            0.9.8.4  COMMITTED  Open Secure Socket License
 openssl.man.en_US          0.9.8.4  COMMITTED  Open Secure Socket Layer
  



 

Configuration of the chroot environment

To start with, you need to choose a chroot directory. We will choose /home/chroot as our chrooted directory.

#mkdir  -p /home/chroot

 

Inside /home/chroot, you need to create the necessary directories and subdirectories like dev, dev/pts, etc, usr, usr/bin, usr/sbin, usr/lib, and tmp.

#pwd
#/home/chroot
#mkdir –p dev/pts etc  usr/bin  usr/sbin  usr/lib/  tmp

 

So now we have following directories in the /home/chroot directory.

/home/chroot # ls -al
total 8
drwxr-xr-x   6 root     system          256 Feb 01 12:07 .
drwxr-xr-x  32 root     system         4096 Feb 01 12:06 ..
drwxr-xr-x   3 root     system          256 Feb 01 12:07 dev
drwxr-xr-x   2 root     system          256 Feb 01 12:07 etc
drwxr-xr-x   2 root     system          256 Feb 01 12:07 tmp
drwxr-xr-x   5 root     system          256 Feb 01 12:07 usr

/home/chroot # ls -al *
dev:
total 0
drwxr-xr-x   3 root     system          256 Feb 01 12:07 .
drwxr-xr-x   6 root     system          256 Feb 01 12:07 ..
drwxr-xr-x   2 root     system          256 Feb 01 12:07 pts

etc:
total 0
drwxr-xr-x   2 root     system          256 Feb 01 12:07 .
drwxr-xr-x   6 root     system          256 Feb 01 12:07 ..

tmp:
total 0
drwxr-xr-x   2 root     system          256 Feb 01 12:07 .
drwxr-xr-x   6 root     system          256 Feb 01 12:07 ..

usr:
total 0
drwxr-xr-x   5 root     system          256 Feb 01 12:07 .
drwxr-xr-x   6 root     system          256 Feb 01 12:07 ..
drwxr-xr-x   2 root     system          256 Feb 01 12:07 bin
drwxr-xr-x   3 root     system          256 Feb 01 12:07 lib
drwxr-xr-x   2 root     system          256 Feb 01 12:07 sbin

/home/chroot # ls -al usr/lib
total 0

drwxr-xr-x   3 root     system          256 Feb 01 12:07 .
drwxr-xr-x   5 root     system          256 Feb 01 12:07 ..

 

Copy binaries and libraries

Copy all of the binaries and the related libraries that are needed for the chroot environment. For ssh login, a shell is necessary (e.g., ksh); for scp the related binary and for sftp access the sftp-server binary are mandatory. In our example, we also chose the commands "cd," "pwd," "ls," "mkdir," "rmdir," "rm," and "cp" that should be allowed in our restricted shell.

The path information for the binaries can be seen with the "which" command and the related libraries can be seen with the "ldd" command. For example, to copy all the binaries and related libraries for the "ls" command, run the following:

# which ls | xargs ldd
/usr/bin/ls needs:
         /usr/lib/libc.a(shr.o)
         /unix
         /usr/lib/libcrypt.a(shr.o)

 

Copy these two libraries to the corresponding path of <chroot-dir> directories.

# cp /usr/lib/libc.a  /home/chroot/usr/lib/ 
# cp /usr/lib/libcrypt.a /home/chroot/usr/lib/

 

All binaries need /unix, as well. Check the /unix directory on the AIX system first:

/home/chroot # ls -al /unix
lrwxrwxrwx   1 root     system           21 Aug 10 2005  /unix -> /usr/lib/boot/unix_64

 

Then create the soft link for /unix as follows:

/home/chroot # ln -s /usr/lib/boot/unix_64 unix

 

Now we have these directories in the chroot directory:

/home/chroot # ls -al
total 8
drwxr-xr-x   6 root     system          256 Feb 01 13:11 .
drwxr-xr-x  32 root     system         4096 Feb 01 12:06 ..
drwxr-xr-x   3 root     system          256 Feb 01 12:07 dev
drwxr-xr-x   2 root     system          256 Feb 01 12:07 etc
drwxr-xr-x   2 root     system          256 Feb 01 12:07 tmp
lrwxrwxrwx   1 root     system           21 Feb 01 13:11 unix -> /usr/lib/boot/unix_64
drwxr-xr-x   5 root     system          256 Feb 01 12:07 usr

 

Similarly, copy all the desired binaries and the libraries needed into the corresponding chroot directory.

 

Create necessary devices

The next step is to create the necessary devices null, zero, tty, and pts/#. The devices in <chroot-dir>/dev should have the same “Major and Minor” and permissions as on the original AIX system. Check the values on the AIX system first, create the devices with "mknod," and assign proper permissions with "chmod" inside the chroot directory. For instance:

/home/chroot # ls –la /dev/tty
crw-rw-rw-   1 root     system        1,  0 Jan 30 13:54 /dev/tty

/home/chroot # ls –la /dev/null
crw-rw-rw-   1 root     system        2,  2 Feb 01 12:49 /dev/null

/home/chroot # ls –la /dev/zero
crw-rw-rw-   1 root     system        2,  3 Aug 10 2005  /dev/zero

 

Now create them in the chroot directory with the mknod command and assign the same permissions as on the original devices:

/home/chroot # mknod dev/tty c 1 0
/home/chroot # mknod dev/null c 2 2
/home/chroot # mknod dev/zero c 2 3

chmod 666 null tty zero

/home/chroot # ls -al dev
total 0
drwxr-xr-x   3 root     system          256 Feb 01 13:49 .
drwxr-xr-x   6 root     system          256 Feb 01 13:11 ..
crw-rw-rw-   1 root     system        2,  2 Feb 01 13:49 null
drwxr-xr-x   2 root     system          256 Feb 01 12:07 pts
crw-rw-rw-   1 root     system        1,  0 Feb 01 13:48 tty
crw-rw-rw-   1 root     system        2,  3 Feb 01 13:49 zero

 

Follow the same steps for pts devices. Normally, it is not necessary to have as many pts/# devices in the chroot as in the general AIX environment. On our test system we use 10 pts/# devices from 0 to 9. So based on the need, the pts devices can be created.

These are the pts devices that we have created for our chroot environment with same permissions as the original pts devices.

/home/chroot # chmod go+w /home/chroot/dev/pts/*

/home/chroot # ls -al /home/chroot/dev/pts/
total 16
drwxr-xr-x   2 root     system         4096 Feb 01 15:01 .
drwxr-xr-x   3 root     system         4096 Feb 01 15:00 ..
crw-rw-rw-   1 root     system       22,  0 Feb 01 15:01 0
crw-rw-rw-   1 root     system       22,  1 Feb 01 15:01 1
crw-rw-rw-   1 root     system       22,  2 Feb 01 15:01 2
crw-rw-rw-   1 root     system       22,  3 Feb 01 15:01 3
crw-rw-rw-   1 root     system       22,  4 Feb 01 15:01 4
crw-rw-rw-   1 root     system       22,  5 Feb 01 15:01 5
crw-rw-rw-   1 root     system       22,  6 Feb 01 15:01 6
crw-rw-rw-   1 root     system       22,  7 Feb 01 15:01 7
crw-rw-rw-   1 root     system       22,  8 Feb 01 15:01 8
crw-rw-rw-   1 root     system       22,  9 Feb 01 15:01 9

/home/chroot # chmod 620 /home/chroot/dev/pts/0
/home/chroot # chown root:security /home/chroot/dev/pts/0
/home/chroot # ls -al /home/chroot/dev/pts/0
crw--w----   1 root     security     22,  0 Feb 01 15:01 /home/chroot/dev/pts/0
/home/chroot # ls -al /dev/pts/0
crw--w----   1 root     security     22,  0 Feb 01 15:09 /dev/pts/0

 

Check chroot configuration

Now that the setup of the basic chroot environment has been finished, check the correct configuration with the chroot command:

/home/chroot # chroot /home/chroot /usr/bin/ksh
/ # ls
dev   etc   tmp   unix  usr
/ # scp -?
scp: illegal option -- ?
usage: scp [-1246BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file]
           [-l limit] [-o ssh_option] [-P port] [-S program]
           [[user@]host1:]file1 [...] [[user@]host2:]file2
/ # cp -?
cp: illegal option -- ?
Usage: cp [-fhipHILPU] [-r|-R] [-E{force|ignore|warn}] [--] src target
   or: cp [-fhipHILPU] [-r|-R] [-E{force|ignore|warn}] [--] src1 ... srcN directory
/ # touch /tmp/test.out
/usr/bin/ksh: touch:  not found
/ # exit

 

Only those commands whose binaries and libraries have been copied can be executed (for example, "ls," "scp" and "cp"). To come out of chroot environment, use "exit."


 

Creating chroot user and finalizing installation

To access this chroot environment remotely using ssh, <chroot-user> has to be created. Normally, the user has a new home directory with the magic token, for example:

<chroot-dir>/./home/<chroot-user>
  

  

In our example, we create the user smile with home directory /home/chroot/./home/smile and /usr/bin/ksh as initial program:

/home/chroot # useradd -s /usr/bin/ksh -m -d
 /home/chroot/./home/smile/ -c "chroot test user" smile

/home/chroot # chown smile:staff /home/chroot/home/smile
/home/chroot # ls -al /home/chroot/home
total 0
drwxr-xr-x   3 root     system          256 Feb 01 18:15 .
drwxr-xr-x   7 root     system          256 Feb 01 18:15 ..
drwxr-xr-x   2 smile    staff           256 Feb 01 18:15 smile

 

Set the password for <chroot-user> and change it on the user shell:

/home/chroot # passwd smile
Changing password for "smile"
smile's New password:
Enter the new password again:
/home/chroot # su - smile
$ passwd
Changing password for "smile"
smile's Old password:
smile's New password:
Enter the new password again:
$ exit

 

Copy <chroot-user> entries from /etc/passwd and /etc/group to the related files in the chroot environment:

/home/chroot # cat /etc/passwd | grep smile >> /home/chroot/etc/passwd

/home/chroot # cat /etc/group | grep smile >> /home/chroot/etc/group

/home/chroot # cat /home/chroot/etc/passwd
smile:!:397:1:chroot test user:/home/chroot/./home/smile/:/usr/bin/ksh

/home/chroot # cat /home/chroot/etc/group
staff:!:1:ipsec,dasusr1,db2inst1,db2fenc1,idsldap,ldapdb2,ftp,anonymou,aroell,
ldap,ituam,ski,usrsftp,sshd,bm,smile

 

Now the chroot environment is complete and can be used with ssh, sftp, and scp, for example:

lp2:root:/root # sftp smile@lp5
Connecting to lp5...
smile@lp5's password:
sftp> ls
sftp> put smit.log
Uploading smit.log to /home/smile/smit.log
smit.log                                      100%  203KB 203.1KB/s   00:00
sftp> ls -al
drwxr-xr-x    2 smile    staff         256 Feb  1 18:32 .
drwxr-xr-x    3 0        0             256 Feb  1 18:15 ..
-rwxr-----    1 smile    staff         254 Feb  1 18:15 .profile
-rw-r--r--    1 smile    staff      207951 Feb  1 18:32 smit.log
sftp> quit

lp2:root:/root # ssh smile@lp5
smile@lp5's password:
Last login: Fri Feb  1 18:32:19 NFT 2008 on ssh from X.YYY.ZZZ.77
$ ls -al
total 424
drwxr-xr-x   2 smile    staff           256 Feb  1 18:33 .
drwxr-xr-x   3 0        0               256 Feb  1 18:15 ..
-rwxr-----   1 smile    staff           254 Feb  1 18:15 .profile
-rw-------   1 smile    staff            10 Feb  1 18:33 .sh_history
-rw-r--r--   1 smile    staff        207951 Feb  1 18:32 smit.log
$ cp smit.log test.out
$ rm smit.log
$ ls -al
total 432
drwxr-xr-x   2 smile    staff           256 Feb  1 18:33 .
drwxr-xr-x   3 0        0               256 Feb  1 18:15 ..
-rwxr-----   1 smile    staff           254 Feb  1 18:15 .profile
-rw-------   1 smile    staff            54 Feb  1 18:33 .sh_history
-rw-r--r--   1 smile    staff        207951 Feb  1 18:33 test.out
$ exit
Connection to lp5 closed.

lp2:root:/root # scp smile@lp5:/home/smile/test.out .
smile@lp5's password:
test.out                                      100%  203KB 203.1KB/s   00:00
lp2:root:/root # ls -al test.out
-rw-r--r--   1 root     system       207951 Feb 01 18:38 test.out




 

Chrooted user with different authentication methods

  • PAM Authentication: Copy the /usr/lib/security/pam_aix in the chrooted directed directory, for example:
    # cp /usr/lib/security/pam_aix    <chroot-dir>/usr/lib/security/
    

     
  • Public Key Authentication: Copy the public key file of the chrooted user in the path mentioned below:
    /home/<chroot-dir>/home/<chroot-user>/.ssh/authorized_keys
    

     

 

Shared library memory footprints on AIX 5L

Learn about shared library mechanisms and memory footprints on IBM® AIX®. This article is essential for developers writing server code or administrators managing production AIX systems. It offers developers and administrators commands and techniques, and gives the understanding necessary to analyze memory requirements of server processes on AIX. It also helps developers and administrators avoid resource shortages that can't be identified with other standard runtime analysis tools such as ps or topas. The article is intended for systems administrators or developers of native applications on AIX.

Introduction

This article examines how shared libraries occupy memory on 32-bit AIX 5L™ (5.3), demonstrating the following commands:

  • ps
  • svmon
  • slibclean
  • procldd
  • procmap
  • genkld
  • genld

The article discusses the virtual address space of processes, as well as the kernel shared-library segment, how to examine them, and how to interpret the output of the various diagnostic utilities mentioned above. The article also discusses how to diagnose situations where the kernel shared segment is full and possible approaches to resolving that situation.

In the examples throughout, we happen to use the processes from the software product Business Objects Enterprise Xir2®. This is arbitrary, as the concepts will apply to all processes running on AIX 5L.



 

Review

Just so we are all in the same mindframe, let's review a little on 32-bit architecture. In doing so, I'll resort to the employ of the most useful 'bc' command-line calculator.

In a 32-bit processor, the registers are capable of holding 2^32 possible values,

 $ bc
 2^32
 4294967296
 obase=16
 2^32
 100000000


 

That is a 4 gigabyte range. This means a program running on the system is able to access any function or data address in the range of 0 and 2^32 - 1.

 $ bc
  2^32 - 1 
 FFFFFFFF
 obase=10
 2^32 - 1 
 4294967295


 

Now, as you know, any operating system has potentially hundreds of programs running at the same time. Even though each one of them is capable of accessing a 4GB range of memory, it doesn't mean that they each get their own 4GB allotment of physical RAM. That would be impractical. Rather, the OS implements a sophisticated scheme of swapping code and data between a moderate amount of physical RAM and areas of the file system designated as swap (or paging) space. Moreover, even though each process is capable of accessing 4GB of memory space, many don't even use most of it. So the OS only loads or swaps the required amount of code and data for each particular process.


Figure 1. Conceptual diagram of virtual memory
vmm
 

This mechanism is often referred to as virtual memory and virtual address spaces.

When an executable file is run, the Virtual Memory Manager of the OS looks at the code and data that comprise the file, and decides what parts it will load into RAM, or load into swap, or reference from the file system. At the same time, it establishes some structure to map the physical locations to virtual locations in the 4GB range. This 4GB range represents the process' maximum theoretical extent and (together sometimes with the VMM's structures that represent it), is known as the virtual address space of the process.

On AIX, the 4GB virtual address space is divided into sixteen 256-megabyte segments. The segments have predetermined functions, some of which are described below:

  • Segment 0 is for kernel-related data.
  • Segment 1 is for code.
  • Segment 2 is for stack and dynamic memory allocation.
  • Segment 3 is for memory for mapped files, mmap'd memory.
  • Segment d is for shared library code.
  • Segment f is for shared library data.

On HP-UX® by comparison, the address space is divided into four quadrants. Quadrants three and four are available for shared library mappings if they are designated using the chatr command with the +q3p enable and +q4p enable options.



 

Where shared libraries are loaded

Shared libraries are, naturally, intended to be shared. More specifically, the read-only sections of the binary image, namely the code (also known as "text") and read-only data (const data, and data that can be copy-on-write) may be loaded once into physical memory, and mapped multiple times into any process that requires it.

To demonstrate this, take a running AIX machine and see which shared libraries are presently loaded:

> su 
# genkld
Text address     Size File

    d1539fe0    1a011 /usr/lib/libcurses.a[shr.o]
    d122f100    36732 /usr/lib/libptools.a[shr.o]
    d1266080    297de /usr/lib/libtrace.a[shr.o]
    d020c000     5f43 /usr/lib/nls/loc/iconv/ISO8859-1_UCS-2
    d7545000    161ff /usr/java14/jre/bin/libnet.a
    d7531000    135e2 /usr/java14/jre/bin/libzip.a
.... [ lots more libs ] ....
d1297108 3a99 /opt/rational/clearcase/shlib/libatriastats_svr.a
[atriastats_svr-shr.o]
    d1bfa100    2bcdf /opt/rational/clearcase/shlib/libatriacm.a[atriacm-shr.o]
    d1bbf100    2cf3c /opt/rational/clearcase/shlib/libatriaadm.a[atriaadm-shr.o]
.... [ lots more libs ] ....
    d01ca0f8     17b6 /usr/lib/libpthreads_compat.a[shr.o]
    d10ff000    30b78 /usr/lib/libpthreads.a[shr.o]
    d00f0100    1fd2f /usr/lib/libC.a[shr.o]
    d01293e0    25570 /usr/lib/libC.a[shrcore.o]
    d01108a0    18448 /usr/lib/libC.a[ansicore_32.o]
.... [ lots more libs ] ....
    d04a2100    fdb4b /usr/lib/libX11.a[shr4.o]
    d0049000    365c4 /usr/lib/libpthreads.a[shr_xpg5.o]
    d0045000     3c52 /usr/lib/libpthreads.a[shr_comm.o]
    d05bb100     5058 /usr/lib/libIM.a[shr.o]
    d05a7100    139c1 /usr/lib/libiconv.a[shr4.o]
    d0094100    114a2 /usr/lib/libcfg.a[shr.o]
    d0081100    125ea /usr/lib/libodm.a[shr.o]
    d00800f8      846 /usr/lib/libcrypt.a[shr.o]
    d022d660   25152d /usr/lib/libc.a[shr.o]

 

As an interesting observation, we can see on this machine right away Clearcase and Java™ are running. Let's take any one of these common libraries, say, libpthreads.a. Browse the library and see which functions it implements:

# dump -Tv /usr/lib/libpthreads.a | grep EXP
[278]   0x00002808    .data      EXP     RW SECdef        [noIMid] pthread_attr_default
[279] 0x00002a68 .data EXP RW SECdef [noIMid]
 pthread_mutexattr_default
[280]   0x00002fcc    .data      EXP     DS SECdef        [noIMid] pthread_create
[281]   0x0000308c    .data      EXP     DS SECdef        [noIMid] pthread_cond_init
[282]   0x000030a4    .data      EXP     DS SECdef        [noIMid] pthread_cond_destroy
[283]   0x000030b0    .data      EXP     DS SECdef        [noIMid] pthread_cond_wait
[284]   0x000030bc    .data      EXP     DS SECdef        [noIMid] pthread_cond_broadcast
[285]   0x000030c8    .data      EXP     DS SECdef        [noIMid] pthread_cond_signal
[286]   0x000030d4    .data      EXP     DS SECdef        [noIMid] pthread_setcancelstate
[287]   0x000030e0    .data      EXP     DS SECdef        [noIMid] pthread_join
.... [ lots more stuff ] ....

 

Hmm, that was cool. Now let's see which currently running processes have it loaded currently on the system:

# for i in $(ps -o pid -e | grep ^[0-9] ) ; do j=$(procldd $i | grep libpthreads.a); \
 if [ -n "$j" ] ; then ps -p $i -o comm | grep -v COMMAND; fi  ; done
portmap
rpc.statd
automountd
rpc.mountd
rpc.ttdbserver
dtexec
dtlogin
radiusd
radiusd
radiusd
dtexec
dtterm
procldd : no such process : 24622
dtterm
xmwlm
dtwm
dtterm
dtgreet
dtexec
ttsession
dtterm
dtexec
rdesktop
procldd : no such process : 34176
java
dtsession
dtterm
dtexec
dtexec

 

Cool! Now let's get the same thing, but eliminate the redundancies:

# cat prev.command.out.txt | sort | uniq 
       
automountd
dtexec
dtgreet
dtlogin
dtsession
dtterm
dtwm
java
portmap
radiusd
rdesktop
rpc.mountd
rpc.statd
rpc.ttdbserver
ttsession
xmwlm

 

There, now we have a nice, discrete list of binaries that are currently executing and all load libpthreads.a. Note that there are many more processes on this system than this at this time:

# ps -e | wc -l  
      85

 

Now, let's see where each process happens to load libpthreads.a :

# ps -e | grep java
 34648      -  4:13 java
#
# procmap 34648 | grep libpthreads.a
d0049000         217K  read/exec      /usr/lib/libpthreads.a[shr_xpg5.o]
f03e6000          16K  read/write     /usr/lib/libpthreads.a[shr_xpg5.o]
d0045000          15K  read/exec      /usr/lib/libpthreads.a[shr_comm.o]
f03a3000         265K  read/write     /usr/lib/libpthreads.a[shr_comm.o]
#
# ps -e | grep automountd
 15222      -  1:00 automountd
 25844      -  0:00 automountd
#
# procmap 15222 | grep libpthreads.a
d0049000         217K  read/exec      /usr/lib/libpthreads.a[shr_xpg5.o]
f03e6000          16K  read/write     /usr/lib/libpthreads.a[shr_xpg5.o]
d0045000          15K  read/exec      /usr/lib/libpthreads.a[shr_comm.o]
f03a3000         265K  read/write     /usr/lib/libpthreads.a[shr_comm.o]
d10ff000         194K  read/exec         /usr/lib/libpthreads.a[shr.o]
f0154000          20K  read/write        /usr/lib/libpthreads.a[shr.o]
#
# ps -e | grep portmap              
 12696      -  0:06 portmap
 34446      -  0:00 portmap
#
# procmap 12696 | grep libpthreads.a
d0045000          15K  read/exec      /usr/lib/libpthreads.a[shr_comm.o]
f03a3000         265K  read/write     /usr/lib/libpthreads.a[shr_comm.o]
d10ff000         194K  read/exec         /usr/lib/libpthreads.a[shr.o]
f0154000          20K  read/write        /usr/lib/libpthreads.a[shr.o]
#
# ps -e | grep dtlogin
  6208      -  0:00 dtlogin
  6478      -  2:07 dtlogin
 20428      -  0:00 dtlogin
#
# procmap 20428 | grep libpthreads.a
d0045000          15K  read/exec      /usr/lib/libpthreads.a[shr_comm.o]
f03a3000         265K  read/write     /usr/lib/libpthreads.a[shr_comm.o]
d0049000         217K  read/exec      /usr/lib/libpthreads.a[shr_xpg5.o]
f03e6000          16K  read/write     /usr/lib/libpthreads.a[shr_xpg5.o]

 

Notice that each process loads it at the same address each time. Don't be confused by the constituent listings for the .o's in the library. On AIX, you can share archive libraries (.a files, customarily) as well as dynamic shared libraries (.so files, customarily). The purpose of this is to be able to bind symbols at link time, just like traditional archive linking, yet not require the constituent object (.o file in the archive) be copied into the final binary image. No dynamic (or runtime) symbol resolution is performed, however, as is the case with dynamic shared libraries (.so/.sl files).

Also note libpthreads.a code sections, those marked read/exec, are loaded into segment 0xd. That segment, as mentioned above, is designated on AIX as the segment for shared library code. That is to say, the kernel loads the shareable segments of this shared library into an area that is shared by all processes running on the same kernel.

You might notice that the data sections are also loaded to the same segment: the shared library segment 0xf. That doesn't mean, however, that each process is also sharing the data section of libpthreads.a. Loosely defined, such an arrangement wouldn't work, as different processes would need to maintain different data values at different times. Segment 0xf is distinct for each process using libpthreads.a, even though the virtual memory address is the same.

The svmon command can show us the segment IDs in the Virtual Memory Manager (Vsid) for processes. We'll see the shared-library code segments all have the same Vsid, while the shared-library data segments all have distinct Vsids. The Esid, meaning Effective Segment ID, is the segment ID within the scope of the process's address space (just terminology; don't let it confuse you).

# svmon -P 17314

-------------------------------------------------------------------------------
     Pid Command          Inuse      Pin     Pgsp  Virtual 64-bit Mthrd  16MB
   17314 dtexec           20245     9479       12    20292      N     N     N

    Vsid      Esid Type Description              PSize  Inuse   Pin Pgsp Virtual
       0         0 work kernel segment               s  14361  9477    0 14361 
   6c01b         d work shared library text          s   5739     0    9  5786 
   19be6         f work shared library data          s     83     0    1    87 
   21068         2 work process private              s     56     2    2    58 
   18726         1 pers code,/dev/hd2:65814          s      5     0    -     - 
    40c1         - pers /dev/hd4:2                   s      1     0    -     - 
#
# svmon -P 20428

-------------------------------------------------------------------------------
     Pid Command          Inuse      Pin     Pgsp  Virtual 64-bit Mthrd  16MB
   20428 dtlogin          20248     9479       23    20278      N     N     N

    Vsid      Esid Type Description              PSize  Inuse   Pin Pgsp Virtual
       0         0 work kernel segment               s  14361  9477    0 14361 
   6c01b         d work shared library text          s   5735     0    9  5782 
   7869e         2 work process private              s     84     2   10    94 
                   parent=786be
   590b6         f work shared library data          s     37     0    4    41 
                   parent=7531d
   6c19b         1 pers code,/dev/hd2:65670          s     29     0    -     - 
   381ae         - pers /dev/hd9var:4157             s      1     0    -     - 
    40c1         - pers /dev/hd4:2                   s      1     0    -     - 
   4c1b3         - pers /dev/hd9var:4158             s      0     0    -     - 


 

Doing the math

Let's see how much is currently in this shared segment 0xd. We'll revert to our bc calculator tool again. So we know we are sane, we'll verify the size of segment 0xd:

# bc    
ibase=16
E0000000-D0000000
268435456
ibase=A
268435456/(1024^2)
256

 

That looks good. Like stated above, each segment is 256MB. Ok, now let's see how much is currently being used.

$ echo "ibase=16; $(genkld | egrep ^\ \{8\} | awk '{print $2}' | tr '[a-f]' '[A-F]' \
 |  tr '\n' '+' ) 0" | bc
39798104
$
$ bc <<EOF
> 39798104/(1024^2)
> EOF
37

 

That is saying that there is 37MB currently being used. Let's start up XIr2, and compare:

$ echo "ibase=16; $(genkld | egrep ^\ \{8\} | awk '{print $2}' | tr '[a-f]' '[A-F]' \
 |  tr '\n' '+' ) 0" | bc
266069692
$
$ bc <<EOF
> 266069692/(1024^2)
> EOF
253

 

Now there is 253MB being used. That is very close to the limit of 256MB. Let's pick a random process, like WIReportServer, and see how many shared libraries made it into shared space, and how many had to be mapped privately. Since we know the shared segment begins at address 0xd000000, we can filter that out of the output from procmap. Remember, only code sections are mapped to segment 0xd, so we'll just look for the lines that are read/exec:

$ procmap 35620 | grep read/exec | grep -v ^d
10000000       10907K  read/exec         boe_fcprocd
31ad3000       14511K  read/exec
/crystal/sj1xir2a/xir2_r/bobje/enterprise115/aix_rs6000/libEnterpriseFramework.so
3167b000        3133K  read/exec
/crystal/sj1xir2a/xir2_r/bobje/enterprise115/aix_rs6000/libcpi18nloc.so
3146c000        1848K  read/exec
/crystal/sj1xir2a/xir2_r/bobje/enterprise115/aix_rs6000/libBOCP_1252.so
31345000         226K  read/exec
/crystal/sj1xir2a/xir2_r/bobje/enterprise115/aix_rs6000/btlat300.so

 

It looks like the above four libraries couldn't be mapped into the shared segment. Consequently, they were mapped to the private segment 0x3, which is used for any general memory allocated by a call to the mmap() routine.

There are a few conditions that force a shared library to be mapped privately on 32-bit AIX:

  • It is out of space in the shared segment 0xd (as above).
  • The shared library does not have execute permissions for group or other. You can use a permission designation of rwxr-xr-x mto correct this; however, developers would want to use private permissions (eg. rwx------) so they don't have to run slibclean each time they recompile a shared library and deploy it for testing.
  • Some documentation says shared libraries are loaded over nfs.

The AIX kernel will even load the same library twice into shared memory, if it comes from a different location:

sj2e652a-chloe:~/e652_r>genkld | grep libcplib.so
        d5180000    678c6 /space2/home/sj2e652a/e652_r/lib/libcplib.so
        d1cf5000    678c6 /home/sj1e652a/xir2_r/lib/libcplib.so


 

When it goes wrong

If we run another instance of XIr2 deployed in a different directory, we see a significant difference in the process footprint:

$ ps -e -o pid,vsz,user,comm | grep WIReportServer
28166 58980   jbrown WIReportServer
46968 152408 sj1xir2a WIReportServer
48276 152716 sj1xir2a WIReportServer
49800 152788 sj1xir2a WIReportServer
50832 152708 sj1xir2a WIReportServer

 

The instance for account 'jbrown' was started first, and the instance for account 'sj1xir2a' was started second. If we were to do something obscure and risky like setting at the appropriate place in our bobje/setup/env.sh file,

    LIBPATH=~jbrown/vanpgaix40/bobje/enterprise115/aix_rs6000:$LIBPATH

 

before starting the second instance, we would see the footprints normalized, (I switch to the process boe_fcprocd, as I couldn't get WIReportServer to start for this LIBPATH test).

$ ps -e -o pid,vsz,user,comm | grep boe_fcprocd   
29432 65036   jbrown boe_fcprocd
35910 67596   jbrown boe_fcprocd
39326 82488 sj1xir2a boe_fcprocd
53470 64964 sj1xir2a boe_fcprocd

 

And we see procmap shows us the files are loaded from ~jbrown as expected:

53470 : /crystal/sj1xir2a/xir2_r/bobje/enterprise115/aix_rs6000/boe_fcprocd
-name vanpg 
10000000       10907K  read/exec         boe_fcprocd
3000079c        1399K  read/write        boe_fcprocd
d42c9000        1098K  read/exec
/home7/jbrown/vanpgaix40/bobje/enterprise115/aix_rs6000/libcrypto.so
33e34160         167K  read/write
/home7/jbrown/vanpgaix40/bobje/enterprise115/aix_rs6000/libcrypto.so
33acc000        3133K  read/exec
/home7/jbrown/vanpgaix40/bobje/enterprise115/aix_rs6000/libcpi18nloc.so
33ddc697         349K  read/write
/home7/jbrown/vanpgaix40/bobje/enterprise115/aix_rs6000/libcpi18nloc.so



 

Clean up

Once applications are shut down, shared libraries may still reside in the shared segment 0xd. In such case, you can use the utility 'slibclean' to unload any shared libraries that are no longer referenced. The utility requires no arguments:

slibclean

 

There is also the utility genld, which when passed the -l option, can show you output like procmap, but for all existing process on the system,

genld -l

 

Sometimes, after running slibclean, you may still be prohibited from copying a shared library. For example:

$ cp /build/dev/bin/release/libc3_calc.so   /runtime/app/lib/
cp: /runtime/app/lib/libc3_calc.so: Text file busy

 

You may have run slibclean already, and running 'genld -l' doesn't show any process having this library loaded. Yet the system still has this file protected. You can overcome this limitation by first deleting the shared library in the target location, and then copying the new shared library:

$ rm /runtime/app/lib/libc3_calc.so
$ cp /build/dev/bin/release/libc3_calc.so   /runtime/app/lib/

 

During shared-library development, if you are making repeated compile, link, execute, and test exercises, you can avoid having to run slibclean in each cycle by making your shared-library executable only by the owner (eg. r_xr__r__). This will cause the process that you use for testing to load and map your shared-library privately. Be sure to make it executable by all, however (e.g. r_xr_xr_x at product release time).



 

Summary

I hope you've been able to see in more detail how shared libraries occupy memory and the utilities used to examine them. With this you'll be better able to assess the sizing requirements for your applications and analyze the constituents of memory footprints for processes running on AIX systems.

Configuring Infiniband for AIX

Infiniband is an interconnect technology that breaks through the bandwidth and fanout limitations of PCI bus by switching from traditional shared bus architecture to a switched fabric architecture. It is a switched fabric I/O technology that ties together servers, storage devices, and network devices. Instead of sending data in parallel, which is what PCI does, Infiniband sends data in serial and can carry multiple channels of data at the same time in a multiplexing signal.

IBM® AIX® 610 supports Infiniband hardware and various protocols that run over Infiniband. This article shows how to configure Infiniband and set up IP over Infiniband interface (IPoIB) in AIX. Also, this article explains how to use RDS (Reliable Datagram Sockets), a protocol (similar to UDP) designed to work over Infiniband to send and receive data using sockets.

Configuring Infiniband

Internet Protocol (IP) packets can be sent over an Infiniband (IB) network interface by using IP over IB (IPoIB). IPoIB encapsulates the IP packets into IB packets and sends using the IB interface. In order to use IPoIB, you must install and configure the ICM driver and at least one IB device in the system. The following steps are required to configure an IB device and then configure IPoIB using the ICM.

  1. Before configuring Infiniband, you need to check to see if the IB device, for instance Infiniband HCA (Infiniband Host Channel Adapter), is configured and is in “Available” state on your AIX box. To check the status, do the following:
    #  lsdev -Cc adapter | grep "host channel"
    iba0 Available  InfiniBand host channel adapter
        

     

    or

    # lsdev -Cc adapter | grep "HCA"
    iba0 
                             Available 01-00 PCIE Dual Port HCA (b3157862)
    

     
  2. Configure ICM (Infiniband Communication Manager). To configure ICM, do the following:

    smit icm -> Add an Infiniband Communication Manager -> Add an Infiniband Communication Manager à select ICM (as the ‘Name of IB Communication manager to Add’) and you will see the screen as shown in Figure 1.



    Figure 1. Configure ICM
    configure

     

    Click Enter to use the default values for each of the fields. The next screen will show ‘Command: OK’ and   ‘icm Available’. ICM configuration is done.

    To check if ICM has been configured, do

    # lsdev -l icm
    icm Available  Infiniband Communication Manager
    

     
  3. Configure Infiniband Network interface. IB interface can be configured using command line interface or SMIT user interface.

    To configure IB interface using SMIT do:

    smit inet -> Change / Show Characteristics of a Network interface -> select ib0 ( IP over Infiniband Network Interface). You will see the screen as shown in Figure 2.



    Figure2. Configure IB interface
    IB

     

    Enter the values for the following fields:

    • Internet Address ( for example, 1.2.3.92)
    • Network mask (for example, 255.255.255.0)
    • HCA adapter (the one we configured in step 1, iba0)
    • Adpater’s port number . There are two ports, Port 1 and Port 2. Use the command ibstat to check which port is Active. If both are active, select that you would like to use, as per your network configuration.
    • Current state – up

     

    Use the default values for the remaining fields.

    The next screen shows ‘Command: OK’ and ‘ib0 changed’. IB interface configuration is done.

    To check the IB interface status, run the ifconfig command.

    # ifconfig ib0
    ib0: flags=e3a0063<UP,BROADCAST,NOTRAILERS,RUNNING,ALLCAST,MULTICAST,GROUPRT>
    
    inet 1.2.3.92 netmask 0xffffff00 broadcast 1.2.3.255
    
    tcp_sendspace 131072 tcp_recvspace 131072 rfc1323 1

     

Configure the IB interface using the command line interface.

Step 1 and step 2 are the same as above. For step 3, do the following:

# /usr/sbin/mkiba  -a 1.2.3.92  -i ib0 -A iba0  -p 2  -P  0xFFFF  -S up 
 -m  255.255.255.0 -M 2044 ib0 changed

 

The syntax for the mkiba command is:

 /usr/bin/mkiba {-a address -i interface -A ib_adapter -p ib_port [-P P_KEY]  
          [-m subnet_mask]
  [-S state] [ -M mtu ] [ -q queue_pair_size ] [ -Q Q_KEY ] [-k superpacket] }

 

where:

-a address IP address of the interface specified by –I (must be in dotted decimal notation)
-i interface Interface to associate with the -a IP address
-A ib_adapter IB adapter associated to the interface
-p ib_port IB port associated with the IB adapter. (Defaults to 1)
-P p_key Partition key associated with the IB port. Key ( Please note that once configured partition key cannot be changed. The user must obtain the Partition key from the network administrator before configuring).
-m subnet_mask Subnetwork mask (dotted decimal or 0x notation)
-S state down,up,detach : The state of the ib interface.
-M ib_mtu HCA MTU required
-q srq_size Send and Receive queue sizes
-Q Q_KEY Q_Key associated with the multicast group
-k superpacket Superpacket feature on or off

Keep the following in mind:

  • The –k option for superpacket is available from AIX 61B and 53N releases onwards; the lower releases do not contain the superpacket feature. Also, when this feature is enabled, it gives a good performance boost. It allows TCP/IP to send 64KB datagrams to the interface, which can increase performance. Note that this feature is supported only in AIX from an AIX host to AIX host, as long as interfaces at both the hosts are enabled with this feature.
  • The –M option for HCA MTU size. AIX supports 4K physical MTU if the switch and the adapter support it. The interface for the first time, expects the user to create the broadcast multicast group in the switch. If the group is not there, always a 2K multicast group will be created by default. So if you have a 4K physical MTU supported adapter and switch, and you don't create the broadcast group in the switch, the interface will lower the MTU to 2K by creating a multicast group of 2K.

 

Run ifconfig to check the IB interface status.

# ifconfig ib0
ib0: flags=e3a0063<UP,BROADCAST,NOTRAILERS,RUNNING,ALLCAST,MULTICAST,GROUPRT>
    inet 1.2.3.92 netmask 0xffffff00 broadcast 1.2.3.255
     tcp_sendspace 131072 tcp_recvspace 131072 rfc1323 1

 

You are done! IB interface is configured.

To verify that all is working well, configure two nodes using the steps indicated above and run ping between the two notes. If ping works, IB is configured properly.



 

Configuring RDS

RDS uses the IB network interface for communication. Thus, IpoIB and IB network interface should be configured in order to use RDS protocol.

Before loading the RDS driver on the systems that you would like to communicate with using RDS, check if the IB network interfaces on those systems are able to ping each other.

Loading RDS

Run the following command to load RDS:

# bypassctrl load rds

 

If you receive the error Exec format error .., the IB interface is not configured. See Configuring Infiniband and configure the IB interface and then try to load RDS using bypassctrl.

If RDS is already loaded, the error /usr/lib/drivers/rds already loaded displays.

To check that the RDS driver was loaded successfully, run the following:

# genkex | grep rds
         47e1000    53770 /usr/lib/drivers/rds

 

If a socket is created to use RDS protocol and returns the error socket: Addr family not supported by protocol, the RDS driver is not loaded and you need to load it. Also, note that on a reboot, the RDS driver gets unloaded and thus needs to be reloaded using the bypassctrl utility after every reboot.



 

rdsctrl utility

Once RDS is loaded, use the rdsctrl (/usr/sbin/rdsctrl) utility to get the RDS statistics for modifying the tuneable parameters and for diagnostics.

The # rdsctrl stats command displays various RDS statistics.

The statistics can be reset using

# rdsctrl stats reset .

Tuning parameters

The following RDS parameters can be tuned after RDS is loaded, but before any RDS application is run. To set any parameter, use the syntax:

# rdsctrl set <tunable parameter>=<value to be set>

 
  • The rds_sendspace parameter refers to the high-water mark of the per-flow send buffer. (There may be multiple flows per socket.)

    The default value is 524288 bytes (512KB). The value is set using the command:

      # rdsctrl set rds_sendspace=<value in bytes>

      

  • rds_recvspace refers to the per-flow high-water mark of the per-socket receive-buffer. For every additional flow to this socket, the receive high-water mark is bumped up by this value.

    The default value is 524288 bytes  (512 KB). The value is set using the command:

    # rdsctrl set rds_recvspace=<value in bytes>

      

    For good RDS streaming performance, the rds_sendspace and rds_recvspace parameters must be at least four times the largest RDS sendmsg size. RDS sends an ACK for each 4 messages received and if the rds_recvspace is not at least 4 times the message size, the throughput will be very low.

  • rds_mclustsize refers to the size of the individual memory cluster, which is also the message fragment size. The default size is 16384 bytes (16KB). The value, always a multiple of 4096, is set using the command: # rdsctrl set rds_mclustsize=<multiple of 4096, in bytes>

    The rds_mclustsize value must be the same on all machines (nodes) in the cluster. Changing this value also has performance implications.

The current values that are set for the tuneable parameters can be retrieved using the command:

   # rdsctrl get <tunable parameter>

 

If this is run without any tuneable parameter, it gives the entire list of tuneable parameters

# rdsctrl get provides the list of tuneable parameters with their current values.

      # rdsctrl get
       rds_conn_block_limit = 100
                  rds_acksz = 180
                  rds_txqsz = 1024
                  rds_rxqsz = 1024
                         rds_mclustsize = 16384
                           rds_recvspace = 524288
                           rds_sendspace = 524288

 

Data-structure dumps

Various RDS structures can be dumped for troubleshooting purposes. The command to use is # rdsctrl dump <structure>

<structure> can be any one of the following:

  • IBC (the details of the IB Reliable Connection)
  • sendcb (the flow details)
  • pcb (the RDS socket PCB details)

 



 

Conclusion

Using the information in this article you learned how to configure Infiniband over AIX and configure and use RDS over Infiniband. You learned the commands used for configuring RDS; however, to understand RDS protocol, refer the Resources section for additional information.

Tips on designing a preprocessor for C++ with Antlr

Learn how to use Antlr to create a C++ preprocessor. Using this approach to create the C++ compiler, you don't need a separate preprocessor engine. Instead, the preprocessor engine can be integrated as part of the lexer.

One of the first steps in creating a custom compiler in C++ is developing the compiler’s preprocessor engine. Typically, C++ compilers have a separate preprocessing engine that accomplishes the following four basic tasks:

  • Defines and redefines macros (#define, #undef).
  • Supports header files (the #include directive).
  • Supports conditional compilation (#ifdef, #ifndef, #else, #endif).
  • Strips all comments from the input source listing.

Typically, the output of the preprocessor engine is then fed to the C++ lexer/parser combo. This article discusses the design of a C++ preprocessor using Antlr. You should have some familiarity with Antlr and the concept of top-down recursive descent parsers. All code discussed in this article is known to work on Antlr-2.7.2 and is compiled using gcc-3.4.4.

Create a single parser for the preprocessor

A salient benefit of using Antlr as your parser-generator tool of choice for creating the C++ compiler is that a separate preprocessor engine isn't necessary. The preprocessor engine may be integrated as part of the lexer. To understand this strategy, recall how lexers and parsers typically work. The lexer processes the raw data (in this case, the .h/.cpp files), creates tokens from this data, and passes the tokens to the parser. The parser in turn processes the tokens and validates against the grammar, does semantic checking, and ultimately generates assembly code.

The key to having a single compiler + preprocessor engine lies in proper modification of the lexer. The Antlr lexer is extended to preprocess the C++ sources and only pass the relevant tokens to the parser. The parser is never aware of the preprocessing part; it's only concerned with the tokens it receives from the lexer. Consider the following snippet:

#define USE_STREAMS
   
#ifdef USE_STREAMS 
#include <iostream>
#else
# include <stdio.h>
#endif 
   

 

Assume that this snippet is declared as part of a C++ source file. The lexer needs to pass on to the parser the first token it finds by including the iostream header. The #define mapping and #ifdef conditional evaluation are the added responsibilities of the lexer.

Enhance the Antlr lexer by defining new tokens

Lexers that work on C++ language define tokens as per the C++ language standard. For example, you usually find tokens that represent variable names and language keywords defined in an average lexer file.

To combine the preprocessing engine with a lexer, you must define new token types corresponding to the preprocessor constructs. On encountering these constructs, the lexer takes the necessary action. The lexer must also strip down comments from the sources. In this case, on encountering the token for a comment, the lexer needs to ignore the token and continue looking for the next available token. It's important to note that these tokens aren't defined as part of the language standard; they're essentially implementation-defined tokens.

Define new tokens in the lexer

The lexer must contain the following tokens, along with language-mandated ones: COMMENT_TOKEN, INCLUDE_TOKEN, DEFINE_TOKEN, UNDEF_TOKEN, IFDEF_TOKEN, ELSE_TOKEN, and ENDIF_TOKEN. This article discusses a prototype lexer that supports the preprocessor macros mentioned earlier and variable declaration for integer and long data types. The barebones lexer/parser combo that processes these tokens is shown in Listing 1.


Listing 1. A bare-bones lexer/parser that supports preprocessor tokens and long/int declarations
 
                
header {
#include "Main.hpp"
#include <string>
#include <iostream>
}

options {
            language = Cpp;
}

class cppParser extends Parser;

start: ( declaration )+ ;
declaration: type a:IDENTIFIER   (COMMA b:IDENTIFIER)* SEMI;
type: INT | LONG; 

{
#include <fstream>
#include "cppParser.hpp"
}

class cppLexer extends Lexer;

options { 
            charVocabulary = '\3'..'\377';
            k=5;
}

tokens { 
  INT="int";
  LONG="long";
}
           
DEFINE_TOKEN: "#define"  WS macroText:IDENTIFIER WS  macroArgs:MACRO_TEXT;
UNDEF_TOKEN: "#undef" IDENTIFIER;  
IFDEF_TOKEN: ("ifdef" | "#ifndef") WS IDENTIFIER;
ELSE_TOKEN: ("else" | "elsif" WS IDENTIFIER);            
ENDIF_TOKEN: "endif";
INCLUDE_TOKEN:  "#include" (WS)? f:STRING;
    
COMMENT_TOKEN: 
  (
  "//" (~'\n')* '\n' { newline( ); } 
   |
  "/*" (
       {LA(2) != '/'}? '*' | '\n' { newline( ); } | ~('*'|'\n')
       )*
  "*/"
  );

IDENTIFIER: ('a'..'z'|'A'..'Z'|'_')('a'..'z'|'A'..'Z'|'_'|'0'..'9')* ;
STRING: '"'! ( ~'"' )* '"'!
SEMI: ';';
COMMA: ',';

WS :  ( ' ' | '\t' | '\f' | '\n' {newline();})+;
            
MACRO_TEXT:  ( ~'\n' )*   ;
    

 

Define the lexer/parser combo to strip comments

Comments can be written in either the C++ // style or the C style /* */ multiline comment style. The lexer is extended to include a rule for the COMMENT_TOKEN. On encountering this token, the lexer needs to be explicitly told not to pass this token to the parser, but instead to skip it and continue looking for the next available token. You do this using Antlr's $setType function. You don't need to add support at the parser end, because it will never pass the comment token. See Listing 2.


Listing 2. Defining the lexer to strip comments from the C/C++ code
 
                
COMMENT_TOKEN: 
  (
  "//" (~'\n')* '\n' { newline( ); } 
   |
 "/*" (
           {LA(2) != '/'}? '*' | '\n' { newline( ); } | ~('*'|'\n')
         )*
  "*/"
  )
  { $setType(ANTLR_USE_NAMESPACE(antlr)Token::SKIP); };
      

 

The code is simple enough for C++-style comments. For the C-style comment, note the {LA(2) != '/'}? used before matching *. It means that if there's a / after the *, then this rule isn't to be matched, and the lexer ends up matching */. This is necessary because /* this is */ an invalid comment */ is an invalid C++ comment. This also implies that the minimum look-ahead needed by the lexer is 2. LA(2) is known as a semantic predicate -- a hint to the lexer to decide what character to look for next in the input stream.



 

Include file processing

As discussed earlier, the parser needs to see a continuous stream of tokens. The lexer therefore needs to switch streams if and when it encounters an included file and switch back to the previous stream on encountering the end of the included file. The parser isn't aware of this stream switch and essentially treats included files as a continuous token stream. You can achieve this behavior several ways using Antlr; for a detailed discussion, see the Resources section.

This article uses Antlr's TokenStreamSelector class. Instead of initializing the parser with the lexer as in Listing 1, the parser is initialized with a TokenStreamSelector object. Internally, the TokenStreamSelector class maintains a stack of lexers. When the lexer encounters a new input stream, it creates a new lexer for processing of the new stream and subsequently attaches the new lexer to the tokenstreamselector object so that all future tokens (until the end of the new stream is reached) are sourced using the new lexer class. This is followed by invoking the retry method of the TokenStreamSelector class, which now sources tokens from the new input stream. The only other thing that remains to be done is to switch back to the previous input stream on encountering the EOF in an included file. To allow for user-defined actions on encountering the end of a line, Antlr provides the predefined routine uponEOF. You must modify this routine to switch to the previous input stream by calling TokenStreamSelector::pop(). Listing 3 shows a code snippet that includes file processing.


Listing 3. Initializing the parser with a TokenStreamSelector object
 
                
#include <iostream>
#include <fstream>
…
TokenStreamSelector selector;
cppParser* parser;
cppLexer* mainLexer;

int main(int argc,char** argv)
{
   try {
       std::ifstream inputstream("test.c", std::ifstream::in);
       mainLexer = new cppLexer(inputstream);
       // notify selector about starting lexer; name for convenience
       selector.addInputStream(mainLexer, "main");
       selector.select("main"); // start with main lexer

       // Create parser attached to selector
       parser = new cppParser (selector);
       parser->setFilename("test.c");
       parser->startRule();
  } 
  catch (exception& e) {
    cerr << "exception: " << e.what() << endl;
  }
  return 0;
} 
      

 

The lexer-related changes are described in Listing 4.


Listing 4. Include file processing in the lexer
 
                
class cppLexer extends Lexer;
…

{
public:
    void uponEOF()  {
        if ( selector.getCurrentStream() != mainLexer ) {
            selector.pop(); // return to old lexer/stream
            selector.retry();
        }
        else {
             ANTLR_USE_NAMESPACE(std)cout << "Hit EOF of main file\n"  ;
        }
    }
}

INCLUDE_TOKEN:  "#include" (WS)? f:STRING
    {
    ANTLR_USING_NAMESPACE(std)
    // create lexer to handle include
    string name = f->getText();
    ifstream* input = new ifstream(name.c_str());
    if (!*input) {
        cerr << "cannot find file " << name << endl;
    }
    cppLexer* sublexer = new cppLexer (*input);
    // make sure errors are reported in right file
    sublexer->setFilename(name);
    parser->setFilename(name);

    // push the previous lexer stream and make sublexer current
    selector.push(sublexer);
    // ignore this token, re-look for token in the switched stream
    selector.retry(); // throws TokenStreamRetryException
    }
    ;
    

 

Note that the TokenStreamSelector object is an intentional global variable because it needs to be shared as part of the lexer uponEOF method. Also, in the uponEOF method post, you must call the pop method retry so the TokenStreamSelector again looks into the current stream for the next token. Note that the include file processing described here isn't complete, but depends on conditional macro support . For example, if a source snippet includes a file within a #ifdef A .. #endif block where A isn't defined, then the processing for INCLUDE_TOKEN is discarded. This is further explained in the Handle macros and Conditional macro support sections.



 

Handle macros

Macro handling is primarily accomplished with the help of a hash table. This article uses the standard STL hash container. In its simplest form, support of macros implies supporting constructs like #define A and #undef A. You can easily do this by pushing the string "A" into a hash table on encountering #define and removing it from the hash on encountering #undef. The hash table is typically defined as part of the lexer. Also, note that the tokens for #define and #undef must not reach the parser -- for this reason, you add $setType(ANTLR_USE_NAMESPACE(Antlr)Token::SKIP); as part of the corresponding token processing. Listing 5 shows this behavior.


Listing 5. Supporting #define and #undef
 
                
class cppLexer extends Lexer;
…

{
bool processingMacro;
std::map<std::string, std::string> macroDefns;
public:
    void uponEOF()  {
    … // Code for include processing
    }
}
…
DEFINE_TOKEN: "#define" {processingMacro=true;} 
    WS macroText:IDENTIFIER WS macroArgs:MACRO_TEXT
  {
  macroDefns[macroText->getText()] = string(macroArgs->getText());
 $setType(ANTLR_USE_NAMESPACE(antlr)Token::SKIP);
  } ;
                           
UNDEF_TOKEN: "#undef" WS macroText:IDENTIFIER
  {
  macroDefns.erase(macroText->getText());
  $setType(ANTLR_USE_NAMESPACE(antlr)Token::SKIP);
  };

MACRO_TEXT: {processingMacro}? ( ~'\n' )* {processingMacro=false;};
      


 

Conditional macro support

Broadly speaking, supporting conditional macros means that you need to account for which tokens reach the parser, depending upon the conditional. For example, look at the following snippet:

#define A
#ifdef A
#ifdef B
int c;
#endif
int d;
#endif
      

 

On encountering the token for the first #ifdef, you must decide whether subsequent tokens that are encountered should be passed on to the parser, depending on whether A has been previously defined. At this stage, you use the hash table defined earlier.

Next, you must consider support of nested #ifdef blocks. Because you need to check for the path condition on encountering each #ifdef, it's logical to maintain a stack for the path conditions. Each #ifdef/#elsif adds the path condition to the stack head; the matching #endif removes the path condition from the stack.

Before you see the code that explains the concept in greater detail, you need to understand another important method in the Antlr lexer class: nextToken, which the parser calls continuously to retrieve the next token from the input stream. You can't modify this routine directly, so the optimal approach is to derive a class from the Antlr cppLexer class and redefine the nextToken method based on the path condition. If the path condition is true, the routine returns the next available token to the parser; otherwise, it continues until the matching #endif is found in the token stream.

Listing 6 shows the source for the derived lexer class. There should be no direct instantiation of cppLexer in the code; the parser should be initialized with a copy of the derived lexer class object.


Listing 6. Defining a new lexer class
 
                
class cppAdvancedLexer : public cppLexer 
  {
  public: 
    cppAdvancedLexer(ANTLR_USE_NAMESPACE(std)istream& in) : cppLexer(in) { }
    RefToken nextToken()
      {
      // keep looking for a token until you don't
      // get a retry exception
      for (;;) {
        try {
          RefToken _next = cppLexer::nextToken();
          if (processToken.empty() || processToken.top()) // defined in cppLexer
            return _next;
          }
        catch (TokenStreamRetryException& /*r*/) {
          // just retry "forever"
          }
        }
      }
  };
    

 

The lexer-related changes are shown in Listing 7.


Listing 7. Conditional macro support in the lexer
 
                
{
public:
    std::stack<bool> processToken;
    std::stack<bool> pathProcessed;
    std::map<std::string, std::list<std::string> > macroDefns;
    void uponEOF() {
    … // Code for include processing defined earlier
    }
}

IFDEF_TOKEN {bool negate = false; } :
  ("#ifdef" | "#ifndef" {negate = true;} ) WS macroText:IDENTIFIER
  {
  bool macroAlreadyDefined = false;
  if (macroDefns.find(macroText->getText()) != macroDefns.end())
    macroAlreadyDefined = true;
  processToken.push(negate? !macroAlreadyDefined : macroAlreadyDefined);
  pathProcessed(processToken.top());
  $setType(ANTLR_USE_NAMESPACE(antlr)Token::SKIP);
  }
  ;
  
ELSE_TOKEN:  ("#else"
  {
   bool& pathCondition = processToken.top();
   pathCondition = !processToken.top(); // no other path is true
   } 
   | 
   "#elsif" WS macroText:IDENTIFIER
    {
     if (!processToken.top() && !pathProcessed.top())  {
       if (macroDefns.find(macroText->getText()) != macroDefns.end()) {
          processToken.push(true);
          pathProcessed.push(true);
       }
     }
     else {
        bool& condition = pathProcessed.top();
        condition = false;
     }
   )
   {
   $setType(ANTLR_USE_NAMESPACE(antlr)Token::SKIP);
   }
   ;

ENDIF_TOKEN: "#endif"
   {
   processToken.pop();
   pathProcessed.pop();
   $setType(ANTLR_USE_NAMESPACE(antlr)Token::SKIP);
   };

 

Examining the code, you see that processToken is defined as a stack of booleans to store the path conditions. On encountering #ifdef, if the code is already in true path, it tests whether the path condition is valid.


 

Support expressions as part of macros

You can use a macro to represent a complex expression that involves numbers, identifiers, mathematical operators, and even function calls. To do so, you must use the macroArgs string to replace every occurrence of the macro definition in the post-processed C++ code; the context determines whether this replacement is syntactically valid. For example, consider this situation: #define A b, c. Somewhere down the line you have int A;, which is valid C++; but switch(A) is clearly invalid C++. You need to parse the stream associated with the string on a per-occurrence basis and let the grammar rules determine the syntactic validity of the context.

The code for this approach works as follows:

  1. Capture the token corresponding to the macroText identifier in the cppAdvancedLexer::nextToken.
  2. Retrieve the macroArgs text from the hash table.
  3. Create an istream for macroArgs and a new copy of the lexer that parses this stream.
  4. Attach this lexer to the TokenStreamSelector object, and call retry -- this fetches the token from the new stream that was just created.

The uponEOF method is provided in cppLexer, which takes care of switching context on encountering the end of the stream. Listing 8 illustrates this discussion.


Listing 8. Modifying the nextToken method the lexer class for macro replacement
 
                
RefToken  cppAdvancedLexer::nextToken()
  {
  // keep looking for a token until you don't
  // get a retry exception
  for (;;) {
    try {
       RefToken _next = cppLexer::nextToken();
       map<string, string>::iterator defI = macroDefns.find(_next->getText());
       if (defI != macroDefns.end() && defI->second != "")
         {
         std::stringstream macroStream;
         cppAdvancedLexer* macrolexer = new cppAdvancedLexer(macroStream);
         macrolexer->setFilename(this->getFilename());
         selector.push(macrolexer);
         selector.retry();
         }
       if (processToken.empty() || processToken.top())
         return _next;
   }
   catch (TokenStreamRetryException& /*r*/) {
          // just retry "forever"
   }
}
            

 

Conclusion

This article discussed the basic methodology you can follow to create a C++ preprocessor using Antlr. Developing a full-fledged C++ preprocessor is beyond the scope of a single article. Although this article doesn't deal with potential errors and error-handling strategies, a real-world preprocessor must account for these factors; for example, the ordering of tokens in conditional macro processing is significant, and you must take steps to ensure that it's handled correctly.