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 shortjust_started
; voidNetdumpController
::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
); } boolNetdumpController
::PacketReceived
(BNetPacket
*pkt
,BNetDevice
*dev
) { static doubletime0
; static ulongpktn
; ushortpkt_size
,i
; ucharmac_addr
[MAC_ADDR_LEN
]; charprotostr
[12]; ushortprotoid
; if (just_started
) {just_started
=false
;pktn
= 0;time0
=system_time
(); } if (pktn
++ ==MAX_PKT_N
) { //There! Now close and cleanupunregister_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? returnfalse
; // else...pkt_size
=pkt
->Size
();fprintf
(f
, "Frame# %u, Length=%d, ",pktn
,pkt_size
);fprintf
(f
, "deltaTime=%10.4f, ", (system_time
() -time0
) / 1000); // PID/lenpkt
->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
) { casePROTOID_IP
:strcpy
(protostr
, "IP"); break; casePROTOID_ARP
:strcpy
(protostr
, "ARP"); break; casePROTOID_RARP
:strcpy
(protostr
, "RARP"); break; casePROTOID_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 sourcefprintf
(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() returnfalse
; // let others enjoy this frame, too } #pragma export on extern "C"BNetProtocol
*open_protocol
( const char *device
) {NetdumpController
*dev
;dev
= newNetdumpController
(); 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:
Almost no frame parsing is spoken here.
Network media is limited to Ethernet only.
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.
Only the received packets are being captured, not the sent ones.
No statistics or characteristics are present. This would include readable addresses, various counters, resource utilization, etc.
Not much in the way of error-checking in the code.
Finally, no UI whatsoever.
To install Netdump, follow these steps:
Copy the binary to the add-ons path, usually:
file:///boot/beos/system/addons/net_server/
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.
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.
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...
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
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.]
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.
classSoundPlayer
{ public: status_tSetSoundFile
(entry_ref *ref
); voidPlay
(void); voidStop
(void); private: static bool_play_back
(void *userData
, char *buffer
, size_tcount
, void *header
); boolPlayback
(char *buffer
, size_tcount
);BDACStream
stream
;BSubscriber
subscriber
;BSoundFile
soundFile
; chartransfer_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_tSoundPlayer
::SetSoundFile
(entry_ref *ref
) { status_terr
;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); } } } returnerr
; }
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:
neighbor
NULL
Since the next parameter (before
)
is false
, the subscriber
will be put at the end of the stream.
before
false
The subscriber will go at the end of the stream.
userData
this
We pass a reference to the SoundPlayer
object to the
stream function. This will make it very easy to manage the
stream and the sound file.
streamFunction
_play_back()
completionFunction
NULL
background
true
The sound will play in the background.
voidSoundPlayer
::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.
voidSoundPlayer
::Stop
(void) {subscriber
.ExitStream
(false
); // Leave the streamsubscriber
.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.
boolSoundPlayer
::_play_back
(void *userData
, char *buffer
, size_tcount
, 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.
boolSoundPlayer
::Playback
(char *buffer
, size_tcount
) { int32frameCount
; // Number of frames to mix int32framesRead
; // Number of frames read from disk int32channelCount
; // Number of channels in sound int32counter
; // Loop counter for mixing int16 *soundData
; // Pointer to the sample to mix int16 *tbuf
; // Short pointer to transfer buffer int32sample
; // 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) { returnfalse
; // 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
) { returnfalse
; } returntrue
; }
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:
Use a larger transfer buffer. Reading in 4K or so at a time is bad for performance. Use a nice, big transfer buffer of, say, 128K or 256K at least, and copy chunks of that buffer into the DAC stream buffer.
Use MixStandardFrames()
or equivalent code to add support for 8-bit
sound formats.
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.