Add k210 linux utilities

This commit is contained in:
Wladimir J. van der Laan 2020-02-21 15:40:43 +00:00
parent 78fb83fe50
commit d9750dcf73
5 changed files with 802 additions and 0 deletions

3
linux/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
esptun
term
*.gdb

15
linux/Makefile Normal file
View File

@ -0,0 +1,15 @@
CROSS ?= /opt/riscv64-uclibc/bin/riscv64-buildroot-linux-uclibc-
CFLAGS = -fPIC -Wl,-elf2flt=-r -Wall
BINARIES = term esptun
all: $(BINARIES)
clean:
rm -f $(BINARIES)
term: term.c
${CROSS}gcc $< -o $@ $(CFLAGS)
esptun: esptun.c
${CROSS}gcc $< -o $@ $(CFLAGS)

66
linux/README.md Normal file
View File

@ -0,0 +1,66 @@
esptun
======
A tool to tunnel IP packets over UDP over WIFI through an UART connected to a ESP8285 with the
standard AT firmware.
Usage
-----
esptun <ifname> <uart> <ssid> <passwd> <host> <port>
This will create tun interface `ifname`. Then, with the with the ESP WIFI
device on `uart` it connects to the AP `ssid` with password `passwd`.
It creates a UDP over IP tunnel with the other endpoint `host:port`.
The new tun device will not be configured, use the `ip` utility to assign an
IP address and switch the interface on.
For example:
/root/esptun tun0 /dev/ttyS1 "accesspointname" "secretpassword" 192.168.122.21 23232
/sbin/ip addr add 10.0.1.2/24 dev tun0
/sbin/ip link set tun0 up
/sbin/ip route add default via 10.0.1.1 dev tun0
Host side
---------
On the other endpoint the tunnel is expected to be a host running `socat` or similar to unwrap
the tunnel. For example:
sudo socat UDP:192.168.2.127:23232,bind=192.168.122.21:23232 \
TUN:10.0.1.1/24,tun-name=tundudp,iff-no-pi,tun-type=tun,su=$USER,iff-up
Optionally, enable forwarding and masquerading:
sudo iptables -t nat -F
sudo iptables -t nat -A POSTROUTING -o ens3 -j MASQUERADE
echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward
Linux kernel
------------
Until there is a proper solution, you can use the following kernel branch
to get the UART working on Kendryte K210 under Linux:
https://github.com/laanwj/linux/tree/kendryte-5.6-rc1-wifi
This is a hack that configures the FPIOA and GPIOHS manually, and configures UART1
from the device tree.
To enable TUN/TAP, enable the following kernel settings:
```
CONFIG_NET=y
CONFIG_INET=y
CONFIG_TUN=y
```
While building the root filesystem, make sure you enable at least `ip` and `ping`
(and possibly other network tools) for busybox.
Author
------
Copyright (c) 2020 W.J. van der Laan
Distributed under the MIT software license,

588
linux/esptun.c Normal file
View File

@ -0,0 +1,588 @@
/** esptun: UDP tunnel over UART, using ESP8285 AT command set.
* Based on "simpletun" by Davide Brini.
*
* Copyright (c) 2020 W.J. van der Laan
* Distributed under the MIT software license,
*/
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>
#include <stdarg.h>
#include <asm/termbits.h>
#include <net/if.h>
#include <linux/if_tun.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/time.h>
/* buffer for reading from UART, must be >= 1500 */
#define ESP_BUFSIZE 2000
/* buffer for reading from tun/tap interface, must be >= 1500 */
#define TAP_BUFSIZE 2000
/* baud rate (max 115200*40 = 4608000) */
#define BAUDRATE (115200 * 40)
/** Macro for writing static strings without needing strlen. */
#define S(s) (const uint8_t*)(s), (sizeof(s)-1)
bool debug = false;
char *progname;
uint8_t esp_buffer[ESP_BUFSIZE];
uint8_t tap_buffer[TAP_BUFSIZE];
size_t tap2net;
size_t net2tap;
/**
* Prints debugging.
*/
static void log_debug(const char *msg, ...)
{
va_list argp;
if (debug) {
va_start(argp, msg);
vfprintf(stderr, msg, argp);
va_end(argp);
}
}
#define INFO1 0
#define INFO2 1
#define WARNING 2
/**
* Prints warning/info.
*/
static void log_info(int cls, const char *msg, ...)
{
va_list argp;
int attr;
switch (cls) {
case INFO1: attr = 95; break;
case INFO2: attr = 35; break;
case WARNING: attr = 91; break;
}
fprintf(stderr, "\x1b[%dm", attr);
va_start(argp, msg);
vfprintf(stderr, msg, argp);
va_end(argp);
fprintf(stderr, "\x1b[0m");
}
static const char hexchars[16] = "0123456789abcdef";
/**
* Log a raw response for debugging.
*/
static void debug_response(const uint8_t *esp_buffer, size_t n) {
if (debug) {
for(size_t i = 0; i < n; ++i) {
if (esp_buffer[i] < 32 || esp_buffer[i] >= 127) {
fputc('\\', stderr);
fputc('x', stderr);
fputc(hexchars[esp_buffer[i] >> 4], stderr);
fputc(hexchars[esp_buffer[i] & 0xf], stderr);
} else {
fputc(esp_buffer[i], stderr);
}
}
}
}
/**
* Prints error message on stderr and exits the program.
*/
static void my_err(char *msg, ...)
{
va_list argp;
fprintf(stderr, "error: ");
va_start(argp, msg);
vfprintf(stderr, msg, argp);
va_end(argp);
fprintf(stderr, "\n");
exit(1);
}
/**
* Allocates or reconnects to a tun/tap device. *dev specifies the name of the
* interface (e.g. tunX). This will be overwritten with the actual name. The
* caller must reserve enough space (IFNAMSIZ) in *dev.
*/
int tun_alloc(char *dev, int flags)
{
struct ifreq ifr;
int fd, err;
const char *clonedev = "/dev/net/tun";
if ((fd = open(clonedev, O_RDWR)) < 0) {
perror("Opening /dev/net/tun");
return fd;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = flags;
if (*dev) {
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
}
if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0) {
perror("ioctl(TUNSETIFF)");
close(fd);
return err;
}
strcpy(dev, ifr.ifr_name);
return fd;
}
/**
* Read routine that checks for errors and exits if an error is
* returned (including unexpected EOF).
*/
static int cread(int fd, uint8_t *buf, int n)
{
int nread;
if ((nread = read(fd, buf, n)) <= 0) {
perror("Reading data");
exit(1);
}
return nread;
}
/**
* Write routine that checks for errors and exits if an error is
* returned (including unexpected EOF).
*/
static int cwrite(int fd, const uint8_t *buf, int n)
{
int nwrite;
if ((nwrite = write(fd, buf, n)) <= 0) {
perror("Writing data");
exit(1);
}
return nwrite;
}
/**
* Ensures we write exactly n bytes. Exit in case of error or EOF.
*/
static void write_all(int fd, const uint8_t *buf, int n)
{
int nwrite, left = n;
while (left > 0) {
nwrite = cwrite(fd, buf, left);
left -= nwrite;
buf += nwrite;
}
}
/**
* Ensures we write exactly n bytes, escaping special characters. Exit in case
* of error or EOF.
*/
static void write_esc(int fd, const char *buf, int n)
{
for (int i = 0; i < n; ++i) {
if (buf[i] == '\\' || buf[i] == ',' || buf[i] == '"') {
write_all(fd, S("\\"));
}
write_all(fd, (const uint8_t*)&buf[i], 1);
}
}
/**
* Write a 32-bit unsigned integer.
*/
static void write_uint(int fd, uint32_t x)
{
char buf[11];
size_t ptr = sizeof(buf);
do {
ptr -= 1;
buf[ptr] = '0' + (x % 10);
x /= 10;
} while (x != 0);
write_all(fd, (const uint8_t*)&buf[ptr], sizeof(buf) - ptr);
}
/**
* Prints usage and exits.
*/
static void usage(void)
{
fprintf(stderr, "Usage:\n");
fprintf(stderr, "%s <ifname> <uart> <ssid> <passwd> <host> <port>\n", progname);
exit(1);
}
/**
* Set UART attributes and speed.
*/
static int setup_uart(int fd, int speed)
{
struct termios2 tty;
if (ioctl(fd, TCGETS2, &tty) != 0) {
my_err("Error from TCGETS2: %s", strerror(errno));
}
tty.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls */
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8; /* 8-bit characters */
tty.c_cflag &= ~PARENB; /* no parity bit */
tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
tty.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */
/* custom baudrate setting */
tty.c_cflag &= ~CBAUD;
tty.c_cflag |= BOTHER;
tty.c_ispeed = speed;
tty.c_ospeed = speed;
/* setup for non-canonical mode */
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
tty.c_oflag &= ~OPOST;
/* fetch bytes as they become available */
tty.c_cc[VMIN] = 1;
tty.c_cc[VTIME] = 1;
if (ioctl(fd, TCSETS2, &tty) != 0) {
my_err("Error from TCSETS2: %s", strerror(errno));
}
return 0;
}
/** Prefix for receive packet. */
static const uint8_t pktprefix[] = {'+', 'I', 'P', 'D', ','};
struct packet_info {
/* header: part after +IPD, and before : */
const uint8_t *hdr;
size_t hdr_len;
/* packet payload */
const uint8_t *payload;
size_t payload_len;
};
/** Determine if a esp_buffer[0..end] contains a complete ESP8285 response.
* If so, return the size (including \r\n line terminators).
* If not, return 0.
*/
static size_t esp_response_is_complete(const uint8_t *b, size_t end, struct packet_info *pinfo)
{
size_t i;
/* Count number of bytes matching prefix. */
for (i = 0; i < sizeof(pktprefix) && i < end && b[i] == pktprefix[i]; ++i)
;
if (i == sizeof(pktprefix)) {
/* Received packet.
* Syntax is +IPD,<len>[,<remote IP>,<remote port>]:<data>
* Look for end of header (':') first.
*/
for (i = 5; i < end && b[i] != ':'; ++i)
;
if (i != end) {
size_t hdr_end = i + 1;
/* Parse length. */
size_t length = atoi((const char*)&b[5]);
if ((hdr_end + length) <= end) {
if (pinfo != NULL) {
pinfo->hdr = &b[5];
pinfo->hdr_len = i - 5;
pinfo->payload = &b[hdr_end];
pinfo->payload_len = length;
}
return hdr_end + length;
}
}
} else if (i == end) {
/* Possive start of received packet response. These are not "\r\n"
* terminated and may contain any character.
*/
return 0;
} else {
/** Other response - look for line terminator. */
for (i = 0; i < end && b[i] != '\n'; ++i)
;
if (i != end) {
return i + 1;
}
}
return 0;
}
static bool is_prefix(const uint8_t *esp_buffer, size_t len, const uint8_t *prefix, size_t prefix_len)
{
if (len < prefix_len) {
return false;
}
for (size_t i = 0; i < prefix_len; ++i)
{
if (esp_buffer[i] != prefix[i]) {
return false;
}
}
return true;
}
/** Get OK/FAIL status after running a command on the ESP. */
static bool esp_read_responses(int fd)
{
/** TODO handle timeouts. */
size_t end = 0;
while (true) {
ssize_t n = read(fd, &esp_buffer[end], ESP_BUFSIZE - end);
if (n <= 0) {
perror("Reading from UART");
exit(1);
}
end += n;
size_t ptr = 0;
while (ptr < end) {
const uint8_t *resp = &esp_buffer[ptr];
size_t len = esp_response_is_complete(resp, end - ptr, NULL);
if (len) {
log_debug("<-: ");
debug_response(resp, len);
log_debug("\n");
/* Check for final response */
if (is_prefix(resp, len, S("OK"))
|| is_prefix(resp, len, S("SEND OK"))) {
return true;
}
if (is_prefix(resp, len, S("FAIL"))
|| is_prefix(resp, len, S("ERROR"))
|| is_prefix(resp, len, S("ALREADY CONNECTED"))) {
return false;
}
/* Log interesting info responses */
if (is_prefix(resp, len, S("+CIPSTA_CUR"))) {
log_info(INFO2, " %.*s\n", len - 11 - 2, resp + 11);
}
if (is_prefix(resp, len, S("WIFI "))) {
log_info(INFO2, " %.*s\n", len - 2, resp);
}
/* On non-final response, keep reading. */
} else {
if (end == ESP_BUFSIZE) {
/* Buffer full but command wasn't complete - this isn't good,
* exit to prevent looping forever. */
my_err("Buffer full with unterminated command");
}
break;
}
ptr += len;
}
/* Remove processed responses from esp_buffer by shifting bytes. */
memmove(esp_buffer, &esp_buffer[ptr], end - ptr);
end -= ptr;
}
}
/** Send a packet to ESP interface. */
static void esp_tx_packet(int fd, const uint8_t *esp_buffer, size_t size) {
write_all(fd, S("AT+CIPSEND="));
write_uint(fd, size);
write_all(fd, S("\r\n"));
if (esp_read_responses(fd)) {
write_all(fd, esp_buffer, size);
esp_read_responses(fd);
}
}
/** Send a packet to tun interface. */
static void tun_tx_packet(int fd, const uint8_t *esp_buffer, size_t size) {
if (write(fd, esp_buffer, size) <= 0) {
log_info(WARNING, "warning: error writing to tun: %s\n", strerror(errno));
}
}
int main(int argc, char **argv)
{
if (argc < 7) {
usage();
}
char if_name[IFNAMSIZ] = "";
const char *portname = argv[2];
const char *ssid = argv[3];
const char *passwd = argv[4];
strncpy(if_name, argv[1], IFNAMSIZ-1);
const char *host = argv[5];
int port = atoi(argv[6]);
int esp_fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
if (esp_fd < 0) {
my_err("Error opening %s: %s", portname, strerror(errno));
}
/* initial: baudrate 115200, 8 bits, no parity, 1 stop bit */
setup_uart(esp_fd, 115200);
/* initialize tun/tap interface */
int tun_fd;
if ((tun_fd = tun_alloc(if_name, IFF_TUN | IFF_NO_PI)) < 0) {
my_err("Error connecting to tun interface %s!\n", if_name);
}
log_info(INFO1, "Successfully connected to interface %s\n", if_name);
log_info(INFO1, "Initializing device\n");
write_all(esp_fd, S("ATE0\r\n"));
if (!esp_read_responses(esp_fd)) {
my_err("Could not disable echo");
}
write_all(esp_fd, S("AT+CWMODE_CUR=1\r\n"));
if (!esp_read_responses(esp_fd)) {
my_err("Could not switch to station mode");
}
write_all(esp_fd, S("AT+CIPMUX=0\r\n"));
if (!esp_read_responses(esp_fd)) {
my_err("Could not switch to single-connection mode");
}
log_info(INFO1, "Changing baudrate to %d\n", BAUDRATE);
write_all(esp_fd, S("AT+UART_CUR="));
write_uint(esp_fd, BAUDRATE);
write_all(esp_fd, S(",8,1,0,0\r\n"));
if (!esp_read_responses(esp_fd)) {
my_err("Could not set faster baudrate");
}
setup_uart(esp_fd, BAUDRATE);
write_all(esp_fd, S("AT\r\n"));
if (!esp_read_responses(esp_fd)) {
my_err("AT test unsuccesful: new baudrate unstable?");
}
log_info(INFO1, "Connecting to AP\n");
write_all(esp_fd, S("AT+CWJAP_CUR=\""));
write_esc(esp_fd, ssid, strlen(ssid));
write_all(esp_fd, S("\",\""));
write_esc(esp_fd, passwd, strlen(passwd));
write_all(esp_fd, S("\"\r\n"));
if (!esp_read_responses(esp_fd)) {
my_err("Could not connect to AP");
}
write_all(esp_fd, S("AT+CIPSTA_CUR?\r\n"));
if (!esp_read_responses(esp_fd)) {
my_err("Could not query IP");
}
log_info(INFO1, "Opening UDP connection to %s:%d from port %d\n", host, port, port);
write_all(esp_fd, S("AT+CIPSTART=\"UDP\",\""));
write_esc(esp_fd, host, strlen(host));
write_all(esp_fd, S("\","));
write_uint(esp_fd, port);
write_all(esp_fd, S(","));
write_uint(esp_fd, port); /* local port is same as remote port for now */
write_all(esp_fd, S("\r\n"));
if (!esp_read_responses(esp_fd)) {
my_err("Could not open UDP connection");
}
log_info(INFO1, "Starting packet loop, moving to background\n");
/* Process to background */
if (daemon(0, 0) < 0) {
my_err("Could not go to background\n");
}
debug = 0; /* no use to log anything after this */
struct pollfd fds[2] = {
{esp_fd, POLLIN, 0},
{tun_fd, POLLIN, 0},
};
do {
for (int i=0; i<2; ++i) {
fds[i].revents = 0;
}
if (poll(fds, 2, 0) < 0) {
perror("poll");
exit(1);
}
/* Handle input from UART */
if (fds[0].revents & POLLIN) {
/* Read all available responses entirely */
size_t end = 0;
while (true) {
ssize_t n = read(esp_fd, &esp_buffer[end], ESP_BUFSIZE - end);
if (n <= 0) {
perror("Reading from UART");
exit(1);
}
end += n;
size_t ptr = 0;
while (ptr < end) {
const uint8_t *resp = &esp_buffer[ptr];
struct packet_info pkt_info;
size_t len = esp_response_is_complete(resp, end - ptr, &pkt_info);
if (len) {
log_debug("<-: ");
debug_response(resp, len);
log_debug("\n");
if (is_prefix(resp, len, pktprefix, sizeof(pktprefix))) {
/* Received UDP packet. */
log_debug("NET2TUN %lu: Read %d bytes from the esp interface\n", net2tap, pkt_info.payload_len);
net2tap++;
tun_tx_packet(tun_fd, pkt_info.payload, pkt_info.payload_len);
}
} else {
if (end == ESP_BUFSIZE) {
/* Buffer full but command wasn't complete - this isn't good,
* exit to prevent looping forever. */
my_err("Buffer full with unterminated command");
}
break;
}
ptr += len;
}
/* Remove processed responses from esp_buffer */
memmove(esp_buffer, &esp_buffer[ptr], end - ptr);
end -= ptr;
if (!end) {
/* All responses processed, back to select. */
break;
}
}
}
// Handle input from TUN
if (fds[1].revents & POLLIN) {
/* data from tun/tap: just read it and write it to the network */
size_t nread = cread(tun_fd, tap_buffer, TAP_BUFSIZE);
log_debug("TUN2NET %lu: Read %d bytes from the tap interface\n", tap2net, nread);
tap2net++;
esp_tx_packet(esp_fd, tap_buffer, nread);
}
} while (1);
return 0;
}

130
linux/term.c Normal file
View File

@ -0,0 +1,130 @@
/** term: Minimal interactive serial console.
* Usage: term /dev/ttyS1
*
* Copyright (c) 2020 W.J. van der Laan
* Distributed under the MIT software license,
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <poll.h>
int set_raw(int fd)
{
struct termios tty;
if (tcgetattr(fd, &tty) < 0) {
printf("Error from tcgetattr: %s\n", strerror(errno));
return -1;
}
tty.c_iflag &= ~(ICRNL | IXON);
tty.c_lflag &= ~(ECHO | ICANON);
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
return 0;
}
int set_interface_attribs(int fd, int speed)
{
struct termios tty;
if (tcgetattr(fd, &tty) < 0) {
printf("Error from tcgetattr: %s\n", strerror(errno));
return -1;
}
cfsetospeed(&tty, (speed_t)speed);
cfsetispeed(&tty, (speed_t)speed);
tty.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls */
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8; /* 8-bit characters */
tty.c_cflag &= ~PARENB; /* no parity bit */
tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
tty.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */
/* setup for non-canonical mode */
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
tty.c_oflag &= ~OPOST;
/* fetch bytes as they become available */
tty.c_cc[VMIN] = 1;
tty.c_cc[VTIME] = 1;
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
return 0;
}
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "Pass serial device on command line\n");
exit(1);
}
const char *portname = argv[1];
int fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0) {
fprintf(stderr, "Error opening %s: %s\n", portname, strerror(errno));
exit(1);
}
set_raw(STDIN_FILENO);
/*baudrate 115200, 8 bits, no parity, 1 stop bit */
set_interface_attribs(fd, B115200);
struct pollfd fds[2] = {
{STDIN_FILENO, POLLIN, 0},
{fd, POLLIN, 0},
};
do {
for (int i=0; i<2; ++i) {
fds[i].revents = 0;
}
if (poll(fds, 2, 0) < 0) {
fprintf(stderr, "Poll error: %s\n", strerror(errno));
exit(1);
}
for (int i=0; i<2; ++i) {
if (fds[i].revents & POLLIN) {
char ch;
int rdlen = read(fds[i].fd, &ch, 1);
if (rdlen < 0) {
fprintf(stderr, "Error from read: %d: %s\n", rdlen, strerror(errno));
exit(1);
} else if (rdlen == 0) {
/* EOF */
exit(0);
}
if (i == 1 && ch == '\r') {
/* Don't print \r */
continue;
}
if (i == 0 && ch == '\n') {
/* Add extra \r before newline */
char cr = '\r';
write(fds[1-i].fd, &cr, 1);
}
int wlen = write(fds[1-i].fd, &ch, 1);
if (wlen <= 0) {
fprintf(stderr, "Error from write: %d: %s\n", wlen, strerror(errno));
exit(1);
}
}
}
} while (1);
}