mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
637 lines
18 KiB
C
637 lines
18 KiB
C
/*
|
|
* Copyright (C) 2016 Kaspar Schleiser <kaspar@schleiser.de>
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License v2. See the file LICENSE for more details.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <linux/if.h>
|
|
#include <linux/if_tun.h>
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/select.h>
|
|
#include <sys/time.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <netdb.h>
|
|
|
|
#include <termios.h>
|
|
|
|
#define MTU 9000
|
|
|
|
#define TRACE(x)
|
|
#define TTY_TIMEOUT_MS (500)
|
|
|
|
#define case_baudrate(val) \
|
|
case val: \
|
|
*baudrate = B ## val; \
|
|
break
|
|
|
|
#define BAUDRATE_DEFAULT B115200
|
|
|
|
#define TCP_DEV "tcp:"
|
|
#define IOTLAB_TCP_PORT "20000"
|
|
|
|
/* Size of serial write buffer */
|
|
#define SERIAL_BUFFER_SIZE 64
|
|
|
|
static void usage(void)
|
|
{
|
|
fprintf(stderr, "Usage: ethos <tap> <serial> [baudrate]\n");
|
|
fprintf(stderr, " ethos <tap> tcp:<host> [port]\n");
|
|
}
|
|
|
|
static void checked_write(int handle, void *buffer, int nbyte)
|
|
{
|
|
while (nbyte > 0) {
|
|
ssize_t res = write(handle, buffer, nbyte);
|
|
if (res <= 0) {
|
|
fprintf(stderr, "write to fd %i failed: %s\n", handle, strerror(errno));
|
|
return;
|
|
}
|
|
nbyte -= res;
|
|
}
|
|
}
|
|
|
|
int set_serial_attribs(int fd, int speed, int parity)
|
|
{
|
|
struct termios tty;
|
|
memset (&tty, 0, sizeof tty);
|
|
if (tcgetattr (fd, &tty) != 0)
|
|
{
|
|
perror ("error in tcgetattr");
|
|
return -1;
|
|
}
|
|
|
|
cfsetospeed (&tty, speed);
|
|
cfsetispeed (&tty, speed);
|
|
|
|
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; /* 8-bit chars*/
|
|
/* disable IGNBRK for mismatched speed
|
|
* tests; otherwise receive break*/
|
|
/* as \000 chars*/
|
|
tty.c_iflag &= ~IGNBRK; /* disable break processing*/
|
|
tty.c_lflag = 0; /* no signaling chars, no echo,*/
|
|
/* no canonical processing*/
|
|
tty.c_oflag = 0; /* no remapping, no delays*/
|
|
tty.c_cc[VMIN] = 0; /* read doesn't block*/
|
|
tty.c_cc[VTIME] = TTY_TIMEOUT_MS / 100; /* 0.5 seconds read timeout*/
|
|
/* in tenths of a second*/
|
|
|
|
tty.c_iflag &= ~(IXON | IXOFF | IXANY); /* shut off xon/xoff ctrl*/
|
|
|
|
tty.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls,*/
|
|
/* enable reading*/
|
|
tty.c_cflag &= ~(PARENB | PARODD); /* shut off parity*/
|
|
tty.c_cflag |= parity;
|
|
tty.c_cflag &= ~CSTOPB;
|
|
tty.c_cflag &= ~CRTSCTS;
|
|
cfmakeraw(&tty);
|
|
|
|
if (tcsetattr (fd, TCSANOW, &tty) != 0)
|
|
{
|
|
perror("error from tcsetattr");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void set_blocking(int fd, int should_block)
|
|
{
|
|
struct termios tty;
|
|
memset (&tty, 0, sizeof tty);
|
|
if (tcgetattr (fd, &tty) != 0)
|
|
{
|
|
perror("error from tggetattr");
|
|
return;
|
|
}
|
|
|
|
tty.c_cc[VMIN] = should_block ? 1 : 0;
|
|
tty.c_cc[VTIME] = TTY_TIMEOUT_MS / 100; /* 0.5 seconds read timeout*/
|
|
/* in tenths of a second*/
|
|
|
|
if (tcsetattr (fd, TCSANOW, &tty) != 0)
|
|
perror("error setting term attributes");
|
|
}
|
|
|
|
/**
|
|
* @brief Escape char definitions
|
|
* @{
|
|
*/
|
|
#define LINE_FRAME_DELIMITER (0x7E)
|
|
#define LINE_ESC_CHAR (0x7D)
|
|
#define LINE_FRAME_TYPE_DATA (0x00)
|
|
#define LINE_FRAME_TYPE_TEXT (0x01)
|
|
#define LINE_FRAME_TYPE_HELLO (0x02)
|
|
#define LINE_FRAME_TYPE_HELLO_REPLY (0x03)
|
|
/** @} */
|
|
|
|
typedef enum {
|
|
WAIT_FRAMESTART,
|
|
IN_FRAME,
|
|
IN_ESCAPE
|
|
} line_state_t;
|
|
|
|
typedef struct {
|
|
line_state_t state;
|
|
int frametype;
|
|
size_t framebytes;
|
|
char frame[MTU];
|
|
char local_l2_addr[6];
|
|
char remote_l2_addr[6];
|
|
} serial_t;
|
|
|
|
/**************************************************************************
|
|
* tun_alloc: allocates or reconnects to a tun/tap device. The caller *
|
|
* needs to reserve enough space in *dev. *
|
|
**************************************************************************/
|
|
int tun_alloc(char *dev, int flags) {
|
|
|
|
struct ifreq ifr = { 0 };
|
|
int fd, err;
|
|
|
|
if( (fd = open("/dev/net/tun", O_RDWR)) < 0 ) {
|
|
perror("Opening /dev/net/tun");
|
|
return fd;
|
|
}
|
|
|
|
ifr.ifr_flags = flags;
|
|
|
|
if (*dev) {
|
|
strncpy(ifr.ifr_name, dev, IFNAMSIZ - 1);
|
|
}
|
|
|
|
if( (err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0 ) {
|
|
perror("ioctl(TUNSETIFF)");
|
|
close(fd);
|
|
return err;
|
|
}
|
|
|
|
strcpy(dev, ifr.ifr_name);
|
|
|
|
return fd;
|
|
}
|
|
|
|
static void _handle_char(serial_t *serial, char c)
|
|
{
|
|
serial->frame[serial->framebytes] = c;
|
|
serial->framebytes++;
|
|
}
|
|
|
|
static int _serial_handle_byte(serial_t *serial, char c)
|
|
{
|
|
switch (serial->state) {
|
|
case WAIT_FRAMESTART:
|
|
if (c == LINE_FRAME_DELIMITER) {
|
|
TRACE("frame start");
|
|
serial->state = IN_FRAME;
|
|
serial->frametype = 0;
|
|
serial->framebytes = 0;
|
|
}
|
|
break;
|
|
case IN_FRAME:
|
|
if (c == LINE_ESC_CHAR) {
|
|
TRACE("i esc");
|
|
serial->state = IN_ESCAPE;
|
|
}
|
|
else if (c == LINE_FRAME_DELIMITER) {
|
|
if (!serial->framebytes) {
|
|
TRACE("i del unexp");
|
|
} else {
|
|
TRACE("i del");
|
|
serial->state = WAIT_FRAMESTART;
|
|
return serial->framebytes;
|
|
}
|
|
}
|
|
else {
|
|
_handle_char(serial, c);
|
|
}
|
|
break;
|
|
case IN_ESCAPE:
|
|
if (c == (LINE_FRAME_DELIMITER ^ 0x20)) {
|
|
_handle_char(serial, LINE_FRAME_DELIMITER);
|
|
}
|
|
else if (c == (LINE_ESC_CHAR ^ 0x20)) {
|
|
_handle_char(serial, LINE_ESC_CHAR);
|
|
}
|
|
else if (c == (LINE_FRAME_TYPE_TEXT ^ 0x20)) {
|
|
TRACE("esc txt");
|
|
serial->frametype = LINE_FRAME_TYPE_TEXT;
|
|
}
|
|
else if (c == (LINE_FRAME_TYPE_HELLO ^ 0x20)) {
|
|
TRACE("esc hello");
|
|
serial->frametype = LINE_FRAME_TYPE_HELLO;
|
|
}
|
|
else if (c == (LINE_FRAME_TYPE_HELLO_REPLY ^ 0x20)) {
|
|
TRACE("esc hello_reply");
|
|
serial->frametype = LINE_FRAME_TYPE_HELLO_REPLY;
|
|
}
|
|
else if (c == LINE_FRAME_DELIMITER) {
|
|
TRACE("esc -del");
|
|
}
|
|
serial->state = IN_FRAME;
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void _write_escaped(int fd, char* buf, ssize_t n)
|
|
{
|
|
/*
|
|
* Certain USB-to-UART adapters/drivers will immediately send a USB packet
|
|
* with a single byte instead of buffering internally when the application
|
|
* does writes one byte at a time. Since USB Full Speed can only send 1
|
|
* packet per 1 ms, this causes huge latencies for the network, because each
|
|
* byte of data will then add at least 1 ms on the latency.
|
|
* Observed on NXP OpenSDAv2 (Kinetis FRDM boards), both CMSIS/mbed DAPlink
|
|
* and Segger Jlink firmware are affected.
|
|
*/
|
|
/* Our workaround is to prepare the data to send in a local buffer and then
|
|
* call write() on the buffer instead of one char at a time */
|
|
uint8_t out[SERIAL_BUFFER_SIZE];
|
|
size_t buffered = 0;
|
|
|
|
while(n--) {
|
|
unsigned char c = *buf++;
|
|
if (c == LINE_FRAME_DELIMITER || c == LINE_ESC_CHAR) {
|
|
out[buffered++] = LINE_ESC_CHAR;
|
|
c ^= 0x20;
|
|
if (buffered >= SERIAL_BUFFER_SIZE) {
|
|
checked_write(fd, out, buffered);
|
|
buffered = 0;
|
|
}
|
|
}
|
|
out[buffered++] = c;
|
|
if (buffered >= SERIAL_BUFFER_SIZE) {
|
|
checked_write(fd, out, buffered);
|
|
buffered = 0;
|
|
}
|
|
}
|
|
if (buffered) {
|
|
checked_write(fd, out, buffered);
|
|
}
|
|
}
|
|
|
|
static void _send_hello(int serial_fd, serial_t *serial, unsigned type)
|
|
{
|
|
char delim = LINE_FRAME_DELIMITER;
|
|
char head[] = { LINE_FRAME_DELIMITER, LINE_ESC_CHAR, (type ^ 0x20) };
|
|
checked_write(serial_fd, head, sizeof(head));
|
|
checked_write(serial_fd, serial->local_l2_addr, 6);
|
|
checked_write(serial_fd, &delim, 1);
|
|
}
|
|
|
|
static void _clear_neighbor_cache(const char *ifname)
|
|
{
|
|
char tmp[20 + IFNAMSIZ];
|
|
snprintf(tmp, sizeof(tmp), "ip neigh flush dev %s", ifname);
|
|
if (system(tmp) < 0) {
|
|
fprintf(stderr, "error while flushing device neighbor cache\n");
|
|
}
|
|
}
|
|
|
|
static int _parse_baudrate(const char *arg, unsigned *baudrate)
|
|
{
|
|
if (arg == NULL) {
|
|
*baudrate = BAUDRATE_DEFAULT;
|
|
return 0;
|
|
}
|
|
|
|
switch(strtol(arg, (char**)NULL, 10)) {
|
|
case 9600:
|
|
*baudrate = B9600;
|
|
break;
|
|
case 19200:
|
|
*baudrate = B19200;
|
|
break;
|
|
case 38400:
|
|
*baudrate = B38400;
|
|
break;
|
|
case 57600:
|
|
*baudrate = B57600;
|
|
break;
|
|
case 115200:
|
|
*baudrate = B115200;
|
|
break;
|
|
/* the following baudrates might not be available on all platforms */
|
|
#ifdef B230400
|
|
case_baudrate(230400);
|
|
#endif
|
|
#ifdef B460800
|
|
case_baudrate(460800);
|
|
#endif
|
|
#ifdef B500000
|
|
case_baudrate(500000);
|
|
#endif
|
|
#ifdef B576000
|
|
case_baudrate(576000);
|
|
#endif
|
|
#ifdef B921600
|
|
case_baudrate(921600);
|
|
#endif
|
|
#ifdef B1000000
|
|
case_baudrate(1000000);
|
|
#endif
|
|
#ifdef B1152000
|
|
case_baudrate(1152000);
|
|
#endif
|
|
#ifdef B1500000
|
|
case_baudrate(1500000);
|
|
#endif
|
|
#ifdef B2000000
|
|
case_baudrate(2000000);
|
|
#endif
|
|
#ifdef B2500000
|
|
case_baudrate(2500000);
|
|
#endif
|
|
#ifdef B3000000
|
|
case_baudrate(3000000);
|
|
#endif
|
|
#ifdef B3500000
|
|
case_baudrate(3500000);
|
|
#endif
|
|
#ifdef B4000000
|
|
case_baudrate(4000000);
|
|
#endif
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int _parse_tcp_arg(char *name, char *port_arg, char **host, char **port)
|
|
{
|
|
/* Remove 'tcp:' */
|
|
name = &name[sizeof(TCP_DEV) - 1];
|
|
|
|
/* Set default if NULL */
|
|
if (!port_arg) {
|
|
port_arg = IOTLAB_TCP_PORT;
|
|
}
|
|
|
|
*host = name;
|
|
*port = port_arg;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Adapted from 'getaddrinfo' manpage example */
|
|
int _tcp_connect(char *host, char *port)
|
|
{
|
|
int sfd = -1;
|
|
struct addrinfo hints, *result, *rp;
|
|
|
|
memset(&hints, 0, sizeof(struct addrinfo));
|
|
hints.ai_family = AF_UNSPEC;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
|
int s = getaddrinfo(host, port, &hints, &result);
|
|
if (s) {
|
|
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
|
|
return -1;
|
|
}
|
|
|
|
/* getaddrinfo() returns a list of address structures.
|
|
Try each address until we successfully connect(2).
|
|
If socket(2) (or connect(2)) fails, we (close the socket
|
|
and) try the next address. */
|
|
for (rp = result; rp != NULL; rp = rp->ai_next) {
|
|
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
|
if (sfd == -1)
|
|
continue;
|
|
|
|
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
|
|
break;
|
|
|
|
close(sfd);
|
|
}
|
|
|
|
freeaddrinfo(result);
|
|
|
|
if (rp == NULL) {
|
|
fprintf(stderr, "Could not connect to '%s:%s'\n", host, port);
|
|
return -1;
|
|
}
|
|
|
|
return sfd;
|
|
}
|
|
|
|
int _set_socket_timeout(int sfd)
|
|
{
|
|
struct timeval timeout = {
|
|
.tv_sec = 0,
|
|
.tv_usec = TTY_TIMEOUT_MS * 1000,
|
|
};
|
|
|
|
if (setsockopt(sfd, SOL_SOCKET, SO_RCVTIMEO,
|
|
(char *)&timeout, sizeof(timeout)) == -1) {
|
|
perror("setsockopt failed\n");
|
|
return 1;
|
|
}
|
|
|
|
if (setsockopt(sfd, SOL_SOCKET, SO_SNDTIMEO,
|
|
(char *)&timeout, sizeof(timeout)) == -1) {
|
|
perror("setsockopt failed\n");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int _open_tcp_connection(char *name, char *port_arg)
|
|
{
|
|
char *host;
|
|
char *port;
|
|
|
|
int ret = _parse_tcp_arg(name, port_arg, &host, &port);
|
|
if (ret) {
|
|
fprintf(stderr, "Error while parsing tcp arguments\n");
|
|
return -1;
|
|
}
|
|
|
|
int sfd = _tcp_connect(host, port);
|
|
if (_set_socket_timeout(sfd)) {
|
|
fprintf(stderr, "Error while setting socket options\n");
|
|
return -1;
|
|
}
|
|
return sfd;
|
|
}
|
|
|
|
int _open_serial_connection(char *name, char *baudrate_arg)
|
|
{
|
|
unsigned baudrate = 0;
|
|
if (_parse_baudrate(baudrate_arg, &baudrate) == -1) {
|
|
fprintf(stderr, "Invalid baudrate specified: %s\n", baudrate_arg);
|
|
return 1;
|
|
}
|
|
|
|
int serial_fd = open(name, O_RDWR | O_NOCTTY | O_SYNC);
|
|
|
|
if (serial_fd < 0) {
|
|
fprintf(stderr, "Error opening serial device %s\n", name);
|
|
return -1;
|
|
}
|
|
|
|
set_serial_attribs(serial_fd, baudrate, 0);
|
|
set_blocking(serial_fd, 1);
|
|
|
|
return serial_fd;
|
|
}
|
|
|
|
int _open_connection(char *name, char* option)
|
|
{
|
|
if (strncmp(name, TCP_DEV, strlen(TCP_DEV)) == 0) {
|
|
return _open_tcp_connection(name, option);
|
|
} else {
|
|
return _open_serial_connection(name, option);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
char inbuf[MTU];
|
|
char *serial_option = NULL;
|
|
|
|
serial_t serial = {0};
|
|
|
|
if (argc < 3) {
|
|
usage();
|
|
return 1;
|
|
}
|
|
|
|
if (argc >= 4) {
|
|
serial_option = argv[3];
|
|
}
|
|
|
|
char ifname[IFNAMSIZ] = { 0 };
|
|
strncpy(ifname, argv[1], IFNAMSIZ - 1);
|
|
int tap_fd = tun_alloc(ifname, IFF_TAP | IFF_NO_PI);
|
|
|
|
if (tap_fd < 0) {
|
|
return 1;
|
|
}
|
|
|
|
int serial_fd = _open_connection(argv[2], serial_option);
|
|
if (serial_fd < 0) {
|
|
fprintf(stderr, "Error opening serial device %s\n", argv[2]);
|
|
return 1;
|
|
}
|
|
|
|
fd_set readfds;
|
|
int max_fd = (serial_fd > tap_fd) ? serial_fd : tap_fd;
|
|
|
|
fprintf(stderr, "----> ethos: sending hello.\n");
|
|
_send_hello(serial_fd, &serial, LINE_FRAME_TYPE_HELLO);
|
|
|
|
fprintf(stderr, "----> ethos: activating serial pass through.\n");
|
|
_send_hello(serial_fd, &serial, LINE_FRAME_TYPE_HELLO);
|
|
int stdin_open = 1;
|
|
while(1) {
|
|
int activity;
|
|
FD_ZERO(&readfds);
|
|
if (stdin_open) {
|
|
FD_SET(STDIN_FILENO, &readfds);
|
|
}
|
|
FD_SET(tap_fd, &readfds);
|
|
FD_SET(serial_fd, &readfds);
|
|
activity = select( max_fd + 1 , &readfds , NULL , NULL , NULL);
|
|
|
|
if ((activity < 0) && (errno != EINTR))
|
|
{
|
|
perror("select error");
|
|
}
|
|
|
|
if (FD_ISSET(serial_fd, &readfds)) {
|
|
ssize_t n = read(serial_fd, inbuf, sizeof(inbuf));
|
|
if (n > 0) {
|
|
char *ptr = inbuf;
|
|
while (n--) {
|
|
size_t res = _serial_handle_byte(&serial, *ptr++);
|
|
if (res) {
|
|
switch (serial.frametype) {
|
|
case LINE_FRAME_TYPE_DATA:
|
|
checked_write(tap_fd, serial.frame, serial.framebytes);
|
|
break;
|
|
case LINE_FRAME_TYPE_TEXT:
|
|
checked_write(STDOUT_FILENO, serial.frame, serial.framebytes);
|
|
break;
|
|
case LINE_FRAME_TYPE_HELLO:
|
|
case LINE_FRAME_TYPE_HELLO_REPLY:
|
|
if (serial.framebytes == 6) {
|
|
memcpy(serial.remote_l2_addr, serial.frame, 6);
|
|
if (serial.frametype == LINE_FRAME_TYPE_HELLO) {
|
|
fprintf(stderr, "----> ethos: hello received\n");
|
|
_send_hello(serial_fd, &serial, LINE_FRAME_TYPE_HELLO_REPLY);
|
|
} else {
|
|
fprintf(stderr, "----> ethos: hello reply received\n");
|
|
}
|
|
_clear_neighbor_cache(ifname);
|
|
}
|
|
break;
|
|
}
|
|
memset(&serial, '\0', sizeof(serial));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
fprintf(stderr, "lost serial connection.\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (FD_ISSET(tap_fd, &readfds)) {
|
|
ssize_t res = read(tap_fd, inbuf, sizeof(inbuf));
|
|
if (res <= 0) {
|
|
fprintf(stderr, "error reading from tap device. res=%zi\n", res);
|
|
continue;
|
|
}
|
|
char delim = LINE_FRAME_DELIMITER;
|
|
checked_write(serial_fd, &delim, 1);
|
|
_write_escaped(serial_fd, inbuf, res);
|
|
checked_write(serial_fd, &delim, 1);
|
|
}
|
|
|
|
if (FD_ISSET(STDIN_FILENO, &readfds)) {
|
|
ssize_t res = read(STDIN_FILENO, inbuf, sizeof(inbuf));
|
|
if (res == 0) {
|
|
fprintf(stderr, "EOF from stdin\n");
|
|
if (isatty(STDIN_FILENO)) {
|
|
/* EOF from the terminal means good bye! */
|
|
fprintf(stderr, "Bye!\n");
|
|
break;
|
|
}
|
|
else {
|
|
/* Ignore EOF when stdin is not a terminal */
|
|
close(STDIN_FILENO);
|
|
stdin_open = 0;
|
|
}
|
|
continue;
|
|
}
|
|
if (res < 0) {
|
|
fprintf(stderr, "error reading from stdio. res=%zi\n", res);
|
|
continue;
|
|
}
|
|
|
|
if (res) {
|
|
char delim = LINE_FRAME_DELIMITER;
|
|
char head[] = { LINE_FRAME_DELIMITER, LINE_ESC_CHAR, (LINE_FRAME_TYPE_TEXT ^ 0x20) };
|
|
checked_write(serial_fd, head, sizeof(head));
|
|
_write_escaped(serial_fd, inbuf, res);
|
|
checked_write(serial_fd, &delim, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|