0

Summary: I am writing a ttyUSB snooper. The code is supposed to read(2) data from a serial port and write(2) it over to another serial port. Reading from the serial port works great with /bin/cat but fails with my code.

Hardware Setup is: I made an FTDI cross-over cable, and put one end in the Windows XP machine as Com2 and the other end in a modern Linux machine as /dev/ttyUSB0. The Linux machine has a USB to Serial cable that shows up as /dev/ttyUSB1. It is connected to the actual hardware unit I am trying to snoop. I verified, the hardware works great.

This part works: I will "cat /dev/ttyUSB0 > /tmp/data" and then have the WinXP machine issue the "read from the device over Com2", and the following six (6) bytes data will be sent over.

\x02\x01\x40\x00\x0a\x9e

This "packet" is sent 4 times or so with a very slight delay, this appears to be the WinXP code just trying a few times. If I replay this just once, it works.

If I simply do "cat /tmp/data > /dev/ttyUSB1", the hardware device will respond properly suggesting that it received the command. Great!

Problem and My Code: I am writing some code to be run on the Linux machine that will read(2) from /dev/ttyUSB0 and write it over to /dev/ttyUSB1 and vice versa. However, for some unknown reason it will only receive 4 bytes in the first "packet", then 5 in the subsequent 3 attempts. Sometimes, the 5 bytes appear slightly "damaged", meaning I see \xffffff for the last or second to last byte. Here is my code:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <strings.h>

void hexdump(char *data, int size) {
    for (size_t i = 0; i < size; ++i)
        printf("%02x ", data[i]);
    putchar('\n');
}

int main() {
    int fdzero;
    int fdone;

    int maxfd;

    fd_set sockrd;
    struct timeval timer;

    char data[10];
    int size;
    char tmp;

    fdzero = open("/dev/ttyUSB0", O_RDWR);
    if (fdzero == -1) {
        perror("Failed to open /dev/ttyUSB0");
        return 1;
    }

    fdone =  open("/dev/ttyUSB1", O_RDWR);
    if (fdone == -1) {
        perror("Failed to open /dev/ttyUSB1");
        return 1;
    }

    if (fdzero > fdone)
        maxfd = fdzero;
    else
        maxfd = fdone;

    printf("Enter loop\n");
    for(;;) {
        bzero(data, 10);
//      fflush(NULL);
        FD_ZERO(&sockrd);
        FD_SET(fdzero, &sockrd);
        FD_SET(fdone,  &sockrd);

        timer.tv_sec = 60;
        timer.tv_usec = 0;

        select(maxfd+1, &sockrd, NULL, NULL, &timer);

        if (FD_ISSET(fdzero, &sockrd)) {

            size = read(fdzero, data, 10);
            if (size == -1) {
                perror("Failed to read /dev/ttyUSB0");
                break;
            }

            size = write(fdone, data, size); 
            if (size == -1) {
                perror("Failed to write to /dev/ttyUSB1");
                break;
            }
            printf("ttyUSB0 -> ttyUSB1: %d\n", size);
        }


        // This portion does not trigger yet, but its a mirror
        // Yes, I know...bad code :(
        else {
            size = read(fdone, data, 10);
            if (size == -1) {
                perror("Failed to read /dev/ttyUSB1");
                break;
            }
            size = write(fdzero, data, size);
            if (size == -1) {
                perror("Failed to write to /dev/ttyUSB0");
                break;
            }
            printf("ttyUSB1 -> ttyUSB0: %d\n", size);
        }

        // Used to monitor what is read()/write()
        hexdump(data, size);
    }

    return 0;
}

When I actually run this code, I see this:

# cc snoop.c -o snoop
# ./snoop
Enter loop
ttyUSB0 -> ttyUSB1: 4
02 00 40 ffffff9e 
ttyUSB0 -> ttyUSB1: 4
02 00 40 ffffff9e 
ttyUSB0 -> ttyUSB1: 4
02 00 40 ffffff9e 
ttyUSB0 -> ttyUSB1: 5
01 02 00 40 ffffff9e 
ttyUSB0 -> ttyUSB1: 5
01 02 00 40 ffffff9e 
ttyUSB0 -> ttyUSB1: 5
01 02 00 40 ffffff9e

Notice that only 4 or 5 bytes are being received and subsequently sent over at any given time. Not 6. Also, notice that the "packet" is distorted. What in the world would cause that???

Rationality if you're interested: I have old software that ONLY runs on Windows XP and will NOT work in a VM (this is a known issue). I would love to capture the traffic going over the serial port. I purchased a WinXP machine just to do this.

Farhan Yusufzai
  • 297
  • 6
  • 23

1 Answers1

-1

Okay, so your two questions here:

Sometimes, the 5 bytes appear slightly "damaged", meaning I see \xffffff for the last or second to last byte.

This is because of how printf interprets the data coming in(this may be of interest). It is being passed in as a char, which is signed. The high-order bits are being interpreted in this case. To fix this part, your hexdump(char* data, int len) should be either hexdump(unsigned char* data, int len) or use a byte-sized type such as uint8_t, so that your signature looks like hexdump(uint8_t* data, int len).

However, for some unknown reason it will only receive 4 bytes in the first "packet", then 5 in the subsequent 3 attempts.

This is almost certainly due to the fact that you do not set any settings on the serial port. One of the characters that you have is 0x0A, which is the linefeed character. This is either being ignored by the serial port driver, or translated into a different character all together. To fix this, you must set the serial port settings to be raw and not translate any characters that come in. I generally do something like the following:

struct termios newio;
if( tcgetattr( fd, &newio ) < 0 ){ /* error handling here */ }    

/* Set some default settings */
newio.c_iflag |= IGNBRK;
newio.c_iflag &= ~BRKINT;
newio.c_iflag &= ~ICRNL;
newio.c_oflag = 0;
newio.c_lflag = 0;
newio.c_cc[VTIME] = 0;
newio.c_cc[VMIN] = 1;

/* Set our baud rate */
cfsetospeed( &newio, B9600 );
cfsetispeed( &newio, B9600 );

/* Character size = 8 */
newio.c_cflag &= ~CSIZE;
newio.c_cflag |= CS8;

/* One stop bit */
newio.c_cflag &= ~CSTOPB;

/* Parity = none */
newio.c_iflag &= ~IGNPAR;
newio.c_cflag &= ~( PARODD | PARENB );
newio.c_iflag |= IGNPAR;

/* No flow control */
newio.c_iflag &= ~( IXON | IXOFF | IXANY );

/* Set our serial port settings */
if( tcsetattr( fd, TCSANOW, &newio ) < 0 ) { /* error handling code here */ }

If you don't want to set the serial port settings this way, I've written a small library which should abstract the small details out for you.

Community
  • 1
  • 1
rm5248
  • 2,590
  • 3
  • 17
  • 16
  • That looked like it did the trick! Oddly enough, it works for a while and then suddenly the connection stream fails. I am not certain why and the software I am snooping in on does not produce an error message. Are there any other connection settings that might result in a better connection? – Farhan Yusufzai Oct 02 '16 at 23:32
  • There aren't any other settings that I can think of. The settings that I posted I derived from [GTKTerm](https://fedorahosted.org/gtkterm/), although I may have missed a setting. – rm5248 Oct 03 '16 at 00:36