Issue 3-3, January 21, 1998

Be Engineering Insights: Sniffing the Wires

By Igor Eydelnant

For network administration or low-level development it's useful to be able to monitor and interpret the network traffic at the physical level with a network monitor/analyzer. These tools are expensive, however, and they pose security concerns and moral dilemmas that need to be resolved.

For network development, though, it can also be helpful to watch the traffic as it passes through the layers of the protocol stack. Every layer strips the corresponding envelope from a received packet on its way up. The reverse process of adding headers and/or trailers takes place when a packet is sent out. Examples of protocol stacks are TCP/IP, AppleTalk, and IPX.

A protocol is simply a set of rules. Protocols are present wherever there is a need to communicate: language, auto traffic rules, human behavior... Not to mention networks and computers. If protocol is broken, communication is #@$#@$%@!

Technically, today's sample is a protocol driver, but instead of passing packets up, it unconditionally rejects each of them after spitting it out to a file. "Reject" in protocolese means: "I'm not consuming this packet, it's meant for some other protocol stack."

Here's what Netdump's output looks like:

...
Frame# 2, Length=60, deltaTime= 1288.5380, Protocol=ARP, PID=0x0806
Dst=[ff:ff:ff:ff:ff:ff], Src=[00:c0:f0:22:c0:81], Entire frame follows
    ff ff ff ff ff ff 00 c0 f0 22 c0 81 08 06 00 01
    08 00 06 04 00 01 00 c0 f0 22 c0 81 cf 71 d7 de
    00 00 00 00 00 00 cf 71 d7 dd 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00

Frame# 3, Length=98, deltaTime=    0.4600, Protocol=IP, PID=0x0800
Dst=[00:60:08:a2:33:0f], Src=[00:c0:f0:22:c0:81], Entire frame follows
    00 60 08 a2 33 0f 00 c0 f0 22 c0 81 08 00 45 00
...

The complete code is available by anonymous ftp at: ftp://ftp.be.com/pub/samples/network_kit/obsolete/netdump.zip

Here is a self-explanatory code excerpt.

#include <NetDevice.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <OS.h>
#include <byteorder.h>
#include "netdump.h"

static FILE *f;
static short just_started;

void
NetdumpController::AddDevice(
                            BNetDevice *dev,
                            const char *name
                            )
{
    if (dev->Type() != B_ETHER_NET_DEVICE)
        return;

    f = fopen(DUMPTO, "w+");
    if (!f) return;

    just_started = true;
    register_packet_handler(this, dev, NETDUMP_PRIO);
}

bool
NetdumpController::PacketReceived(
                                 BNetPacket *pkt,
                                 BNetDevice *dev
                                 )
{
    static double time0;
    static ulong pktn;
    ushort pkt_size, i;
    uchar mac_addr[MAC_ADDR_LEN];
    char protostr[12];
    ushort protoid;

    if (just_started) {
        just_started = false;
        pktn = 0;
        time0 = system_time();
    }

    if (pktn++ == MAX_PKT_N) { //There! Now close and cleanup
        unregister_packet_handler(this, dev);
        fclose(f);
        // From now on, only counter will get incremented.
        // No output
    }
    if (pktn >= MAX_PKT_N)      // are we "there" yet?
        return false;
    // else...

    pkt_size = pkt->Size();

    fprintf(f, "Frame# %u, Length=%d, ", pktn, pkt_size);
    fprintf(f, "deltaTime=%10.4f, ",
            (system_time() - time0) / 1000);

    // PID/len
    pkt->Read(ETHDR_PROTOID_OFS, (char *)&protoid,
      sizeof(protoid));
    protoid =
      B_BENDIAN_TO_HOST_INT16(protoid); // net is big-endian
    // interpret it
    if (protoid > MAX_ETH_LEN) {
        switch (protoid) {
        case PROTOID_IP:
            strcpy(protostr, "IP");
            break;
        case PROTOID_ARP:
            strcpy(protostr, "ARP");
            break;
        case PROTOID_RARP:
            strcpy(protostr, "RARP");
            break;
        case PROTOID_APLTK:
            strcpy(protostr, "AppleTalk");
            break;
        default:
            strcpy(protostr, "#Unknown#");
            break;
        }
        fprintf(f, "Protocol=%s, ", protostr);
    } // else //This is IEEE 802.3 len. See RFC 894, 1042...

    // frame destination...
    fprintf(f, "PID=0x%04x\nDst=[", protoid);
    pkt->Read(0, (char *) &mac_addr, MAC_ADDR_LEN);
    for (i = 0; i < MAC_ADDR_LEN; i++)
        fprintf(f, "%02x%s", (uchar) mac_addr[i],
            (i == MAC_ADDR_LEN -1) ? "], " : ":");

    // ...and source
    fprintf(f, "Src=[");
    pkt->Read(MAC_ADDR_LEN, (char *) &mac_addr,
              MAC_ADDR_LEN);
    for (i = 0; i < MAC_ADDR_LEN; i++)
        fprintf(f, "%02x%s", (uchar) mac_addr[i],
            (i == MAC_ADDR_LEN -1) ? "], " : ":");

    fprintf(f, "Entire frame follows");
    for (i = 0; i < pkt_size; i++) {
        fprintf(f, "%s%02x", (i % 0x10) == 0 ? "\n\t" : " ",
            (uchar) (pkt->Data())[i]);
    }
    fprintf(f, "\n"); fflush(f);
    time0 = system_time();   // there's always next time()
    return false;        // let others enjoy this frame, too
}

#pragma export on
extern "C" BNetProtocol *
open_protocol(
              const char *device
              )
{
    NetdumpController *dev;

    dev = new NetdumpController();
    return (dev);
}
#pragma export reset

Thanks to the BNetDevice and BNetPacket classes, most of the job is already done for us. A "normal" protocol would do a more useful job in PacketReceived(), which could include checksumming, filtering, interpreting headers/trailers, assembling a packet out of fragments, and finally delivering it.

Netdump is not a tcpdump port but rather a simple debug utility. A quick look at a serious network monitor is enough to reveal the shortcomings in this sample. Needless to say, all of its features were carefully selected and organically grown for today's presentation.

Current limitations and areas for improvement:

  1. Almost no frame parsing is spoken here.

  2. Network media is limited to Ethernet only.

  3. Only direct and broadcast packets are being captured because the promiscuous mode was not activated on the interface. If you're planning to enable this feature, please be aware that not all network card drivers in PR2 support switching to promiscuous mode. This will be fixed in the near future.

  4. Only the received packets are being captured, not the sent ones.

  5. No statistics or characteristics are present. This would include readable addresses, various counters, resource utilization, etc.

  6. Not much in the way of error-checking in the code.

  7. Finally, no UI whatsoever.

To install Netdump, follow these steps:

  1. Copy the binary to the add-ons path, usually:

    file:///boot/beos/system/addons/net_server/

  2. Back up the file ~/config/settings/network. Make sure you have the following line in it:

    PROTOCOLS = "netdump"
    

    Other protocols may be present inside the quotes, too.

  3. Press "Restart Network" button.

Currently netdump captures only the first MAX_PKT_N packets, so it's safe to leave it working overnight on a crowded subnet. To stop sniffing earlier, just remove the "netdump" string from the PROTOCOLS line (undo your changes) and restart the network.

Thanks to Bradley Taylor for his help and to my kids for lending me their computer over the holidays.


Be Engineering Insights: Newsletter Article Version 4.1.2b6

By Ron Theis

Recently, I found myself working once again as team commander for a fighting force attempting to save the world. This time, I was with the Global Defense Initiative, and I was trying to stop the evil Brotherhood of Nod from taking over the planet. I was part way through my mission and was feeling pretty confident after taking out two of the three gun turrets marked for destruction, when suddenly, my old foe "Sorry, System Error. Unimplemented Trap" reared his ugly head. Blast it, I was so close! Now the Brotherhood would conquer the world while I was hitting "Restart"!

After some investigating, I found that my Mac demo of "Command & Conquer" was crashing because of the extension DrawSprocketLib (thank you MacsBug). I went to the Apple web site to find out more, since, after all, it was an Apple-supplied extension which was giving me the trouble.

In half an hour of searching, I found references to versions 1.0.6 and 1.0.8, but nothing about 1.1 or the latest version. I found source code for integrating 1.0.6 into an application (yippee), but nothing about downloading the latest version of DrawSprocketLib. Eventually, I reinstalled the demo and it installed a beta version of DrawSprocketLib 1.1; somehow the older version hadn't been replaced during my earlier install.

So I was thinking (as I often do) that it would be great if I could check the versions of my applications without having to search for each one individually. I have at least a hundred applications on my hard drive, and I don't want to visit a separate web site to check the version status of each one. I want to scan my hard drive and check with a central repository of information to see if I've got the latest versions of everything.

But in order to do that, gosh, I'd need an up-to-date archive of application information. The archive would need to encompass absolutely every application available on a platform and be maintained by the application developers themselves. Like BeWare.

So I wrote a little application called VersionCheck which does exactly that. It searches through a hard drive, finds all the applications, and checks with the Be web site for the latest versions. The original DR8 version of VersionCheck was clunky and slow, but the most recent PR2 version is clunky and fast. It returns information about the latest available versions of your installed applications, and provides FTP and HTTP links for folks to download an updater or read more about the latest version.

VersionCheck is not as smooth as Software Valet, which allows your computer to check for the latest versions of your installed packages and upgrade them while you sleep. But if you're interested in testing out a raw, proof-of-concept application, contact me at and I'll send you a copy. It's not perfect, and it will expose applications on your drive that don't have their version resource information set correctly. How well it works also depends on whether the application developer has kept their BeWare entry up-to-date.

I'll post VersionCheck to BeWare within the next week or two, so if you'd rather not bother with rawness, please don't. I'm basically interested in seeing whether something like this would get used—unless I'm the only person who has this problem....

A sister application to VersionCheck, of course, is one that checks BeWare for newly available applications. When I want to know what games have been released in the last two months, I don't want to scour several gaming web sites for tidbits. I just want a list that shows up in a window, with links to download or find out more information. This sister app is on the drawing board, but again, maybe I'm alone on this one...

Always With The Questions

And now, a standard feature of my articles—a few of the recent questions I've been asked regarding the BeOS at trade shows and via: webmaster@be.com

Q:

Can I have a beta copy of BeOS for Intel? I've got a 1400 MHz whoozitz 8000 and a truckload of RAM and this crankin' video card and...

A:

This is easily the number one question webmaster@be.com receives. We realize there's a great deal of pent-up demand for BeOS for Intel, and that people will visit our web site just to scrounge for BeOS for Intel information (our web site access logs bear this out; BeOS for Intel documents are grabbed the most).

But seriously, like the FAQs say, we aren't in need of additional beta testers at this point. Really. You are, however, encouraged to sign up for more information on the BeOS for Intel Info sign-up form, at http://www.be.com/users/intel_info.html, and you'll get the absolute latest BeOS for Intel information as it becomes available.

Q:

Can I get real work done using the BeOS?

A:

Yes indeed. The entire Be engineering staff runs the BeOS exclusively, performing all of their day-to-day work in the BeOS. No kidding. Give it a try, I'll bet you can do it too.

Q:

So, Ron, what's your pick for "Sleeper BeOS Application of the Month? "

A:

Glad you asked! It's called "Content." Content is a programmer's reference to The Be Book, presenting a hierarchical listing of chapters and classes available. With a double-click, it takes you to the correct document in The Be Book via NetPositive. It isn't perfect yet, but with a little fine-tuning and improved NetPositive support for anchors, it can be really useful...

That's it from the web side of things. For folks who haven't seen the new BeWare subcategory layouts or the excellent Developer Library on the site yet, do check them out. As always, your comments, especially on the revamped sections, are welcome at webmaster@be.com.

[This article was written using Pe on the BeOS. Try it, you'll like it.]


Developers' Workshop: Daddy, Where Does Sound Come From?

By Eric Shepherd

Way back in mid-November (Issue 2-#45 of the Be Newsletter, to be exact), I wrote an article called "Sounds That Go Bump In the Night," which described in fairly excruciating detail how to create a sound mixing function that handles multiple sound formats.

This brings up the obvious follow-up question: where does sound come from? How does sound get from a file on disk into the audio stream?

Don't be embarrassed. Lots of programmers ask this question at some point in their lives.

The Media Kit provides the BSoundFile class, which provides an incredibly easy mechanism for reading sounds from disk. This week, we'll create a simple class to play back sound files from disk.

The SoundPlayer class discussed here is only capable of playing 16-bit audio. You should be able to easily change this code to use the MixStandardFrames() function described in Issue 2-#45 of the Be Newsletter to support both 8 and 16-bit sound. I didn't want to take all the adventure out of sound programming.

As always, let's start by looking at the class SoundPlayer, so we can figure out in advance what we'll be working on.

class SoundPlayer {
  public:
    status_t  SetSoundFile(entry_ref *ref);
    void      Play(void);
    void      Stop(void);

  private:
    static bool _play_back(void *userData,
                           char *buffer,
                           size_t count,
                           void *header);
    bool Playback(char *buffer, size_t count);
    BDACStream   stream;
    BSubscriber  subscriber;
    BSoundFile   soundFile;
    char         transfer_buf[B_PAGE_SIZE];

    };

The public API for the SoundPlayer class consists of three functions: SetSoundFile() is used to specify the sound file to be played. Play() begins playback of the sound file, and Stop() halts playback.

There are two private functions: _play_back() is the stream function we'll be using with the DAC stream, and Playback() is called by _play_back() to do the real work of mixing the sound data into the stream. I'll get into this in more detail when I describe the functions themselves.

The class also includes a BDACStream object, a BSubscriber, and a BSoundFile, as well as a buffer (transfer_buf) we'll be using for loading the sound data from disk.

Let's start by having a look at the SetSoundFile() function. It accepts a single parameter—an entry_ref specifying the sound file to play.

status_t SoundPlayer::SetSoundFile(entry_ref *ref) {
  status_t  err;
  err = soundFile.SetTo(ref, B_READ_ONLY);

The function starts by setting the BSoundFile, soundFile, to the specified entry_ref, and marks the BSoundFile as read-only. An additional function performed by the BSoundFile class's SetTo() function is that it examines the file and determines what format the sound data is in. Once we've called SetTo(), we can use the various functions of the BSoundFile class to determine the format of the sound data our soundFile object represents.

  if (err == B_OK) {
    if (soundFile.SampleSize() != 2) {
      err = B_ERROR;    // Reject file if not 16-bit
    }

If the SetTo() is successful, we then check to be sure the file contains 16-bit audio, since our sample mixer is only capable of mixing 16-bit sound. This is done by calling soundFile.SampleSize(), which returns the size of each sound sample in bytes. If the sample size isn't two bytes, we return B_ERROR.

    else {
      err = subscriber.Subscribe(&stream);
      if (err == B_OK) {
        stream.SetSamplingRate(soundFile.SamplingRate());
        stream.SetStreamBuffers(B_PAGE_SIZE, 8);
      }
    }
  }
  return err;
}

If the sound is 16-bit, we ask our subscriber to subscribe to the BDACStream called stream. If that returns B_OK, meaning that no error occurred, we set the BDACStream's sampling rate to match that of soundFile, and set the stream buffers for the stream to be the same size as our class's transfer buffer by calling SetStreamBuffers(). Now we turn our attention to the incredibly simple Play() function. All the Play() function does is enter the subscriber into the stream by calling our subscriber's EnterStream() function. As a refresher, the parameters to EnterStream() are:

void SoundPlayer::Play(void) {
    subscriber.EnterStream(NULL, false, this, _play_back,
                           NULL, true);
}

And the Stop() function, shown below, stops playback by removing the subscriber from the stream and then unsubscribing it from the stream.

void SoundPlayer::Stop(void) {
  subscriber.ExitStream(false);  // Leave the stream
  subscriber.Unsubscribe();      // And unsubscribe from it
}

The stream function, _play_back() simply calls through to the Playback() function, where the real work of playing the sound is done.

bool SoundPlayer::_play_back(void *userData, char *buffer,
                             size_t count, void *header) {
  return (((SoundPlayer *) userData)->Playback(buffer, count));
}

Now let's look at the real heart of the SoundPlayer class. The Playback() function is responsible for reading frames of audio data from the sound file and mixing them into the DAC stream for playback. It accepts two parameters: buffer, which is a pointer to the sound buffer that the sound file data should be mixed into, and count, which is the size of the buffer in bytes.

bool SoundPlayer::Playback(char *buffer, size_t count) {
  int32  frameCount;    // Number of frames to mix
  int32  framesRead;    // Number of frames read from disk
  int32  channelCount;  // Number of channels in sound
  int32  counter;       // Loop counter for mixing
  int16  *soundData;    // Pointer to the sample to mix
  int16  *tbuf;         // Short pointer to transfer buffer
 int32  sample;   // Temporary value of sample while mixing

We begin by establishing a 16-bit pointer to the DAC stream buffer passed into the Playback() function, and a local 16-bit pointer to the transfer_buf that's defined in the SoundPlayer class.

  soundData = (int16 *) buffer;
  tbuf = (int16 *) transfer_buf;

Then we compute the number of frames that we need to read from disk to fill the buffer, and cache locally the number of channels in the sound (whether it's stereo or mono):

  frameCount = count/4;
  channelCount = soundFile.CountChannels();

Then we read in the appropriate number of frames, to fill the transfer_buf buffer. If we read zero frames, or a negative result is returned (which indicates an error occurred), we return false, which indicates that the sound has finished playing and that we want to be automatically removed from the stream.

  framesRead =
    soundFile.ReadFrames(transfer_buf, frameCount);
  if (framesRead <= 0) {
    return false;      // Either error or done with file
  }

Once we've loaded the next few frames of sound from disk, it's time to mix the sound into the buffer. This should look familiar if you read "Sounds That Go Bump In the Night," but, briefly, here's how it works: For each frame of audio, we add the sample already in the DAC buffer to the sample in the transfer buffer. If the sound we're playing is stereo (if channelCount is 2) we do the same thing again to cover the right channel, otherwise we just increment the soundData pointer to leave the DAC stream's right channel alone.

Note that we handle clipping; if the sum of the two samples is outside the range -32768...32768, we clip it to the appropriate value.

  counter = 0;
  do {
    sample = *soundData + *tbuf++; //Add the old and the new
    if (sample > 32767) {         // Is the result too high?
      sample = 32767;
    }
    else if (sample < -32768) {   // How about too low?
      sample = -32768;
    }
    *soundData++ = sample;    // Now save the clipped value
    if (channelCount == 2) {  // If there's another channel,
      sample = *soundData + *tbuf++; //do same thing again
      if (sample > 32767) {
        sample = 32767;
      }
      else if (sample < -32768) {
        sample = 32768;
      }
      *soundData++ = sample;
    }
    else {
      soundData++; // Just skip the right channel
    }
  } while (++counter < framesRead);

Finally, if the number of frames read from disk is less than the number of frames we could have put in the DAC buffer, we return false, since that means we've played the entire sound. Otherwise, we return true, so our subscriber will remain in the stream.

  if (framesRead < frameCount) {
    return false;
  }
  return true;
}

To play a sound, just use the following code:

  SoundPlayer player;
  player.SetSoundFile(sound_file_ref);
  player.Play();

If you want to stop the sound:

  player.Stop();

This is a pretty basic sound file player. You can beef it up without too much effort, and I just happen to have a couple of suggestions for things you might try doing:


The March 19-20 Be Developer Conference

By Jean-Louis Gassée

Allow me to add a few words to what my associates have already said regarding the upcoming Developer Conference. This one is a turning point in the life of the company and its developers, as it will mark our first real step into the Intel space, moving from demos to CDs, with all the attendant expectations and nervousness.

As discussed earlier, the players, the competition, the hardware technology, the buying habits are different from what we've seen on the PowerPC, not merely bigger. As a result, the March '98 Be DC will focus on both product and market strategy for the new Intel version of the BeOS. If past conferences are any indication, we know we can look forward to articulate, energetic discussions of technical as well as business issues.

There is more good news. We still are a very small company with little staff; so, instead of platoons of product managers, PR flacks, middle managers and other acetate-flingers and PowerPoint users, you'll be able to interact directly with the Be team, including the engineers who designed and wrote the BeOS—and use it every day in advancing the platform. Since this release is not just a port of the previous release, but has many important new features of its own, direct contact with the engineers is essential. For their part, the engineers are always nervous before the conference, but are happy after meeting with people who are building a product and a business using their work.

Still on the good news, you'll see existing, shipping, revenue-making BeOS applications, as well as works-in-progress from your colleagues and competitors. Of course, we'll try to put a special emphasis on work showing off the best examples of real-time WYSIWYG benefits, on features, speed, rendering, and acquisition performance not available on other platforms.

On that last topic, we'll re-state our position vis-à-vis Windows. In contrast to an example we don't want to follow—OS/2, a better DOS than DOS, a better Windows than Windows—we offer Linux and happy coexistence with Windows, as a complement or supplement, rather than an improbable replacement.

We'll need to clarify the differences, technical and otherwise, between the BeOS and the various versions of Linux, the latest one looking very good, what we can learn, what we'll do differently—but that's for another column.

You're welcome to attend, even if you're not a registered developer. This is an opportunity for us to make our case, and for you to make a decision.

But there is some bad news. Seating is limited and, if informal polling is worth anything, the Intel version will increase participation. So, please take a minute and register on our site. As a consideration for your effort, we'll give you a $20 early registration discount.

I look forward to seeing you March 19-20 in Santa Clara.

Creative Commons License
Legal Notice
This work is licensed under a Creative Commons Attribution-Non commercial-No Derivative Works 3.0 License.