by Jan Schiefer, DL5UE and Dieter Deyke, DK5SG/N0PRA
English translation by Mike Chace (G6DHU) February 1993
At the end of 1990, the Stuttgart Packet Radio Group began to think in earnest about the security of data between a TNC and a WAMPES [*] node. It was noted that many other Packet node systems suffered from loss of data on the serial lines and effort was put into devising an extension of the KISS protocol to overcome this.
The result of this development was SMACK (Stuttgart's Modified Amateur CRC KISS). This document introduces SMACK and its differences over normal KISS so that implementations for other systems can be developed.
The KISS protocol was developed in 1986 by Phil Karn (KA9Q) and Mike Cheppionis (K3MC) [1]. The need for KISS was born out of the requirement for a simple interface from TCP/IP software to the AX.25 Protocol environment. KISS provides a level 2a protocol interface. It provides channel access through CSMA/CD and the P-Persistence algorithm together with the conversion from synchronous HDLC data on the channel to asynchronous data on an RS232 serial channel.
KISS organizes serial "packets" through a known delimiter and defines a simple command set enabling TNC parameters to be set. Later, improvements to the protocol were devised allowing a single serial channel to share data from more than one Packet Radio channel.
The host computer communicates with KISS in the form of packets. The start of each packet is recognized through the FEND delimiter. The Command Byte then follows. This determines whether the rest of the packet contains a command or data from the HDLC channel. Apart from the Reset Command ($FF), all commands use only the lower 4 bits for the Command Byte. It is this property that allows multi channel TNCs to share the same serial channel through using the upper 4 bits as the HDLC channel number. This allows up to 16 HDLC channels to be supported.
Since we knew of no 16 or even 8 channel TNCs, we decided to use the uppermost bit of the Command Byte. If it is set, this signifies that packets with CRC follow. It is important to note that it is only data packets that carry this checksum. The lowest byte of the checksum is received first.
The following diagrams show the frame format of data packets, one with checksum and one without:
+------+------+------+------+-----+------+------+ | FEND | 0x00 | DATA | DATA | ... | DATA | FEND | +------+------+------+------+-----+------+------+ KISS data frame (without checksum) +------+------+------+------+---- | FEND | 0x80 | DATA | DATA | ... +------+------+------+------+---- ---+------+---------+----------+------+ ... |DATA | CRC LOW | CRC HIGH | FEND | ---+------+---------+----------+------+ SMACK data frame (with checksum)
At this point, it should be reiterated that only data frames are secured with the checksum. This prevents command frames being sent to the TNC when Host computer and TNC are not in agreement over whether CRC's are in use.
At startup, a SMACK-capable TNC is in normal KISS mode and therefore does not generate checksums. As soon as the first frame with CRC arrives, the SMACK send routine is invoked. From this point onwards, SMACK can only be exited through a reset. This is also the case for the host computer.
If however, a KISS TNC is present at the end of the serial link, frames from the host containing a CRC will be decoded as unknown Command Bytes and are therefore discarded. KISS operation then continues as normal. This has the advantage that normal and SMACK-capable KISS TNCs can operate using the same host. No changes in host configuration are required.
The KISS/SMACK negotiations are illustrated below:
--------------------------------------------------------------- Example 1: KISS-TNC Host TNC - Sends a single packet with CRC, returns its send routine back to normal KISS mode. - Receives a frame which for it appears to be the unknown Command Byte 0x80 which it duly discards. - Sends KISS data without Checksums. - Sends KISS data without Checksums. ---------------------------------------------------------------
Example 2: SMACK-TNC
Host TNC - Sends a single frame with CRC and switches its send routine back to normal KISS mode. - Receives a frame with CRC signature, switches its send routine into CRC mode. - Sends KISS data without any Checksums. - TNC sends the first frame with CRC. - Receives a frame with the CRC signature, switches its send routine into CRC mode. - Sends all data in SMACK mode with Checksums. - Sends all data in SMACK mode with Checksums. ---------------------------------------------------------------
Regardless of the sender's state (CRC/no CRC), received frames are always handled according to the following rules.
Received Frame | Action --------------------------+-------------- Without CRC | Process Frame With CRC, Checksum OK | Process Frame With CRC, Checksum Bad | Discard Frame
These rules assume that all KISS implementation discard unrecognized frames. This assumption is stated in the KISS Protocol Specification [1].
This is not the right forum in which to discuss the theory of the Cyclic Redundancy Check (CRC). For this information read the work of Michael Roehner, DC4OX [2]. This excerpt merely discusses the essential points that allow other implementations to be developed.
The check polynomial is the standard CRC16 Polynomial. This has the formula:
16 15 2 X + X + X + 1
The CRC generator is primed with 0. The CRC is calculated over all data frames including the Command Byte 0x80.
As discussed before, the KISS protocol delimits frames with the FEND character (0xc0). In the event that this character is present in the data portion of the frame, this is handled by the software. This strategy is called SLIP (Serial Line Internet Protocol) encoding.
The CRC must be calculated before SLIP encoding is carried out and checked after SLIP decoding is complete. This is for a number of reasons:
The CRCs belong logically to the KISS layer.
The calculation algorithm is as follows:
Various calculation algorithms for CRC generators are discussed in [2]. Here is a simple table-driven generator written in the C language, which generates the CRC of a buffer (buf) of length n.
/*-------------------------------------------------------*/ static int calc_crc(char *buf, int n) { static int crc_table[] = { 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040 }; int crc; crc = 0; while (--n >= 0) crc = ((crc >> 8) & 0xff) ^ crc_table[(crc ^ *buf++) & 0xff]; return crc; } /*--------------------------------------------------------*/
The table requires some 512 bytes of memory. If this is too great for a simple TNC EPROM implementation, the table can be built up at runtime as in the following example:
/*--------------------------------------------------------*/ unsigned short Table[256]; const int Rest[8] = { 0xC0C1, 0xC181, 0xC301, 0xC601, 0xCC01, 0xD801, 0xF001, 0xA001 }; main() { int i, j; unsigned short value; for (i = 0; i < 256; i++) { value = 0; for (j = 0; j < 8; j++) if (i & (1 << j)) value ^= Rest[j]; Table[i] = value; } } /*----------------------------------------------------------*/
If this algorithm is coded in assembler, it requires less space than that taken by the table itself. The theory can be found in [2].
The SMACK protocol has been implemented in the following systems:
One development on the wish list would be an implementation of the SMACK protocol in the standard Packet Driver format [3]. This would then allow any existing version of NOS or NET to use SMACK without any changes to the source code. In any case, all Packet Radio software can benefit from the extra security that SMACK affords.