PREFIX ?= /usr/local
DESTDIR ?=
-TARGETS=hexin hexout devio llcio pcscio
+TARGETS=hexin hexout devio mbusio
+# Dropped llcio, since LLC is not commonly used
+# TARGETS += llcio
+# Dropped pcscio, as it requires uncommon headers
+# TARGETS += pcscio
LITERAL=derdump
all: $(TARGETS)
pcscio: pcscio.c
$(CC) -I /usr/include/PCSC $(CFLAGS) $(PCSCFLAGS) -o pcscio pcscio.c -lpcsclite
+mbusio: mbusio.c socket.c
+ $(CC) $(CFLAGS) -o mbusio mbusio.c socket.c
+
install: all
install $(TARGETS) $(LITERAL) "$(DESTDIR)$(PREFIX)/sbin"
--- /dev/null
+/* mbusio.c -- input/output through Modbus TCP */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <sys/types.h>
+#include <sys/select.h>
+
+#include "socket.h"
+
+
+int get16 (char *msg) {
+ return ((int) msg [0]) | (((int) msg [1]) << 8);
+}
+
+void set16 (char *msg, int val) {
+ msg [0] = val & 0x00ff;
+ msg [1] = (val >> 8) & 0x00ff;
+}
+
+
+int main (int argc, char *argv []) {
+ fd_set sel;
+ int busy=1;
+ if ((argc < 1) || (argc > 3)) {
+ fprintf (stderr, "Usage: %s [addr [port]]\n where addr defaults to 127.0.0.1 and port defaults to 502\nSend and receive slave+PDU over Modbus TCP, so each message is formatted\nSLAVE/8,FUNCTION/8,DATA/n\nThe TCP headering is handled by this utility\n", argv [0]);
+ exit (1);
+ }
+ char *addr = "127.0.0.1";
+ if (argc >= 2) {
+ addr = argv [1];
+ }
+ char *port = "502";
+ if (argc >= 3) {
+ port = argv [2];
+ }
+ struct sockaddr_storage ss;
+ if (!socket_parse (addr, port, (struct sockaddr *) &ss)) {
+ perror ("Cannot parse address and/or port");
+ exit (1);
+ }
+ int sox;
+ if (!socket_client ((struct sockaddr *) &ss, SOCK_STREAM, &sox)) {
+ perror ("Cannot connect to Modbus TCP");
+ exit (1);
+ }
+ int txnid_send = 1;
+ int txnid_recv = 1;
+ while (busy) {
+ FD_ZERO (&sel);
+ FD_SET (sox, &sel);
+ FD_SET (0, &sel);
+ if (select (sox+1, &sel, NULL, NULL, NULL) < 0) {
+ perror ("Select failed");
+ busy = 0;
+ } else {
+ if (FD_ISSET (sox, &sel)) {
+ char buf [6+256];
+ int len = read (sox, buf, 6+256);
+ if (len < 0) {
+ perror ("Error reading");
+ busy = 0;
+ } else if (len < 7) {
+ fprintf (stderr, "MBAP header too short\n");
+ busy = 0;
+ } else if (get16 (buf+0) != txnid_recv % 65536) {
+ fprintf (stderr, "MBAP txnid is bad\n");
+ busy = 0;
+ } else if (get16 (buf+2) != 0) {
+ fprintf (stderr, "MBAP protoid is bad\n");
+ busy = 0;
+ } else if (get16 (buf+4) != len - 7) {
+ fprintf (stderr, "MBAP length is bad\n");
+ busy = 0;
+ } else if (txnid_recv >= txnid_send) {
+ fprintf (stderr, "Reply without Query\n");
+ busy = 0;
+ } else {
+ if (write (1, buf+6, len-6) < len-6) {
+ perror ("Partial write");
+ busy = 0;
+ }
+ txnid_recv++;
+ }
+ }
+ if (FD_ISSET (0, &sel)) {
+ char buf [6+256];
+ int len = read (0, buf+6, 256);
+ if (len < 0) {
+ perror ("Error on stdin");
+ busy = 0;
+ } else {
+ set16 (buf+0, txnid_send % 65536);
+ set16 (buf+2, 0);
+ set16 (buf+4, len);
+ if (write (sox, buf, 6+len) < 6+len) {
+ perror ("Partial output");
+ busy = 0;
+ }
+ txnid_send++;
+ }
+ }
+ }
+ }
+ close (sox);
+ return 0;
+}
--- /dev/null
+/* Socket utilities, including parsing and sockaddr juggling.
+ *
+ * From: Rick van Rein <rick@openfortress.nl>
+ */
+
+
+#include "socket.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <assert.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+
+
+#ifdef DEBUG
+# include <stdio.h>
+# define DPRINTF printf
+#else
+# define DPRINTF(...)
+#endif
+
+
+#ifndef PART_OF_KXOVER
+#include <errno.h>
+#define kxerrno errno
+#endif
+
+
+/* Given a socket address, determine its length.
+ *
+ * This function does not fail.
+ *
+ * TODO:inline
+ */
+socklen_t sockaddrlen (const struct sockaddr *sa) {
+ assert ((sa->sa_family == AF_INET6) || (sa->sa_family == AF_INET));
+ if (sa->sa_family == AF_INET6) {
+ return sizeof (struct sockaddr_in6);
+ } else {
+ return sizeof (struct sockaddr_in );
+ }
+}
+
+
+/* Store a raw address from a given family in a socket address,
+ * together with a port that may be set to 0 as a catch-all.
+ */
+bool socket_address (sa_family_t af, uint8_t *addr, uint16_t portnr, struct sockaddr *sa) {
+ sa->sa_family = af;
+ memset (sa, 0, sockaddrlen (sa));
+ sa->sa_family = af;
+ switch (af) {
+ case AF_INET6:
+ memcpy (&((struct sockaddr_in6 *) sa)->sin6_addr, addr, 16);
+ ((struct sockaddr_in6 *) sa)->sin6_port = htons (portnr);
+ return true;
+ case AF_INET:
+DPRINTF ("DEBUG: socket address (%d.%d.%d.%d, %d)\n", addr [0], addr [1], addr [2], addr [3], portnr);
+ memcpy (&((struct sockaddr_in *) sa)->sin_addr, addr, 4);
+ ((struct sockaddr_in *) sa)->sin_port = htons (portnr);
+ return true;
+ default:
+ break;
+ }
+ kxerrno = EINVAL;
+ return false;
+}
+
+
+/* Parse an address and port, and store them in a sockaddr of
+ * type AF_INET or AF_INET6. The space provided is large enough
+ * to hold either, as it is defined as a union.
+ *
+ * The opt_port may be NULL, in which case the port is set to 0
+ * in the returned sockaddr; otherwise, its value is rejected
+ * if it is 0.
+ *
+ * We always try IPv6 address parsing first, but fallback to
+ * IPv4 if we have to, but that fallback is deprecated. The
+ * port will be syntax-checked and range-checked.
+ *
+ * Return true on success, or false with kxerrno set on error.
+ */
+bool socket_parse (char *addr, char *opt_port, struct sockaddr *out_sa) {
+ //
+ // Optional port parsing
+ uint16_t portnr = 0;
+ if (opt_port != NULL) {
+ long p = strtol (opt_port, &opt_port, 10);
+ if (*opt_port != '\0') {
+ kxerrno = EINVAL;
+ return false;
+ }
+ if ((p == LONG_MIN) || (p == LONG_MAX) || (p <= 0) || (p > 65535)) {
+ /* errno is ERANGE */
+ kxerrno = errno;
+ return false;
+ }
+ portnr = (uint16_t) p;
+ }
+ //
+ // IPv6 address parsing
+ uint8_t raw_addr [16];
+ switch (inet_pton (AF_INET6, addr, raw_addr)) {
+ case 1:
+ return socket_address (AF_INET6, raw_addr, portnr, out_sa);
+ case 0:
+ break;
+ default:
+ break;
+ }
+ //
+ // IPv4 address parsing
+ switch (inet_pton (AF_INET, addr, raw_addr)) {
+ case 1:
+ return socket_address (AF_INET, raw_addr, portnr, out_sa);
+ case 0:
+ break;
+ default:
+ break;
+ }
+ //
+ // Report EINVAL as an error condition
+ kxerrno = EINVAL;
+ return false;
+}
+
+
+/* Open a connection as a client, to the given address. Do not bind locally.
+ *
+ * Set contype to one SOCK_DGRAM, SOCK_STREAM or SOCK_SEQPACKET.
+ *
+ * The resulting socket is written to out_sox.
+ *
+ * Return true on success, or false with kxerrno set on failure.
+ * On error, *out_sox is set to -1.
+ */
+bool socket_client (const struct sockaddr *peer, int contype, int *out_sox) {
+ int sox = -1;
+ sox = socket (peer->sa_family, contype, 0);
+ if (sox < 0) {
+ goto fail;
+ }
+ if (connect (sox, peer, sockaddrlen (peer)) != 0) {
+ goto fail;
+ }
+#ifdef PART_OF_KXOVER
+ int soxflags = fcntl (sox, F_GETFL, 0);
+ if (fcntl (sox, F_SETFL, soxflags | O_NONBLOCK) != 0) {
+ goto fail;
+ }
+#endif
+ *out_sox = sox;
+ return true;
+fail:
+ *out_sox = -1;
+ if (sox >= 0) {
+ close (sox);
+ }
+ return false;
+}
+
+
+/* Open a listening socket as a server, at the given address.
+ *
+ * Set contype to one of SOCK_DGRAM, SOCK_STREAM or SOCK_SEQPACKET.
+ *
+ * The resulting socket is written to out_sox.
+ *
+ * Return true on success, or false with kxerrno set on failure.
+ * On error, *out_sox is set to -1.
+ */
+bool socket_server (const struct sockaddr *mine, int contype, int *out_sox) {
+ int sox = -1;
+ sox = socket (mine->sa_family, contype, 0);
+ if (sox < 0) {
+ goto fail;
+ }
+ if (bind (sox, mine, sockaddrlen (mine)) != 0) {
+ goto fail;
+ }
+ if ((contype == SOCK_STREAM) || (contype == SOCK_SEQPACKET)) {
+ if (listen (sox, 10) != 0) {
+ goto fail;
+ }
+ }
+#ifdef PART_OF_KXOVER
+ int soxflags = fcntl (sox, F_GETFL, 0);
+ if (fcntl (sox, F_SETFL, soxflags | O_NONBLOCK) != 0) {
+ goto fail;
+ }
+#endif
+ *out_sox = sox;
+ return true;
+fail:
+ *out_sox = -1;
+ if (sox >= 0) {
+ close (sox);
+ }
+ return false;
+}
+
+
--- /dev/null
+/* Socket utilities, including parsing and sockaddr juggling.
+ *
+ * From: Rick van Rein <rick@openfortress.nl>
+ */
+
+
+#ifndef KXOVER_SOCKET_H
+#define KXOVER_SOCKET_H
+
+
+#include <stdbool.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <arpa/inet.h>
+
+#include <errno.h>
+
+
+/* Error codes for the entire KXOVER package, for com_err(), see src/errors.et */
+typedef long kxerr_t;
+extern kxerr_t kxerrno;
+
+
+/* Given a socket address, determine its length.
+ *
+ * This function does not fail.
+ *
+ * TODO:inline
+ */
+socklen_t sockaddrlen (const struct sockaddr *sa);
+
+
+/* Store a raw address from a given family in a socket address,
+ * together with a port that may be set to 0 as a catch-all.
+ */
+bool socket_address (sa_family_t af, uint8_t *addr, uint16_t portnr, struct sockaddr *sa);
+
+
+/* Parse an address and port, and store them in a sockaddr of
+ * type AF_INET or AF_INET6. The space provided is large enough
+ * to hold either, as it is defined as a union.
+ *
+ * The opt_port may be NULL, in which case the port is set to 0
+ * in the returned sockaddr; otherwise, its value is rejected
+ * if it is 0.
+ *
+ * We always try IPv6 address parsing first, but fallback to
+ * IPv4 if we have to, but that fallback is deprecated. The
+ * port will be syntax-checked and range-checked.
+ *
+ * Return true on success, or false with kxerrno set on error.
+ */
+bool socket_parse (char *addr, char *opt_port, struct sockaddr *out_sa);
+
+
+/* Open a connection as a client, to the given address. Do not bind locally.
+ *
+ * Set contype to one SOCK_DGRAM, SOCK_STREAM or SOCK_SEQPACKET.
+ *
+ * The resulting socket is written to out_sox.
+ *
+ * Return true on success, or false with kxerrno set on failure.
+ */
+bool socket_client (const struct sockaddr *peer, int contype, int *out_sox);
+
+
+/* Open a listening socket as a server, at the given address.
+ *
+ * Set contype to one of SOCK_DGRAM, SOCK_STREAM or SOCK_SEQPACKET.
+ *
+ * The resulting socket is written to out_sox.
+ *
+ * Return true on success, or false with kxerrno set on failure.
+ */
+bool socket_server (const struct sockaddr *mine, int contype, int *out_sox);
+
+
+#endif /* KXOVER_SOCKET_H */
+