diff --git a/.gitignore b/.gitignore index efaa3f60..33407e74 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ Makefile.in src/modbus-config.h src/modbus-version.h src/win32/modbus.dll.manifest +src/stamp-h3 tests/unit-test.h # mkdocs diff --git a/README.md b/README.md index 258a6e3c..2204fe51 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,10 @@ You will only need to install automake, autoconf, libtool and a C compiler (gcc or clang) to compile the library and asciidoc and xmlto to generate the documentation (optional). -To install, just run the usual dance, `./configure && make install`. Run -`./autogen.sh` first to generate the `configure` script if required. +To install, just run the usual dance, `./configure && make install`. +Run `./autogen.sh` first to generate the `configure` script if required. +You may be required to use `gmake` on platforms where default `make` is +different (BSD make, Sun make, etc.) You can change installation directory with prefix option, eg. `./configure --prefix=/usr/local/`. You have to check that the installation library path is diff --git a/configure.ac b/configure.ac index fe572c46..40ca3ed1 100644 --- a/configure.ac +++ b/configure.ac @@ -30,6 +30,8 @@ AC_INIT([libmodbus], AC_CONFIG_SRCDIR([src/modbus.c]) AC_CONFIG_AUX_DIR([build-aux]) AM_INIT_AUTOMAKE([check-news foreign 1.11 silent-rules tar-pax subdir-objects]) +AM_PROG_CC_C_O +AC_PROG_CPP AC_PROG_CC AC_USE_SYSTEM_EXTENSIONS AC_SYS_LARGEFILE @@ -89,7 +91,6 @@ AC_CHECK_HEADERS([ \ linux/serial.h \ netdb.h \ netinet/in.h \ - netinet/ip.h \ netinet/tcp.h \ sys/ioctl.h \ sys/params.h \ @@ -101,6 +102,17 @@ AC_CHECK_HEADERS([ \ unistd.h \ ]) +dnl On some platforms like FreeBSD and OpenIndiana (illumos) the +dnl netinet/ip.h requires netinet/in.h explicitly included first: +AC_CHECK_HEADERS([ \ + netinet/ip.h \ +], [], [], [ + AC_INCLUDES_DEFAULT +#if HAVE_NETINET_IN_H +# include +#endif +]) + # Cygwin defines IPTOS_LOWDELAY but can't handle that flag so it's necessary to # workaround that problem and Cygwin doesn't define MSG_DONTWAIT. AC_CHECK_DECLS([__CYGWIN__]) @@ -109,7 +121,7 @@ AC_CHECK_DECLS([__CYGWIN__]) AC_SEARCH_LIBS(accept, network socket) # Checks for library functions. -AC_CHECK_FUNCS([accept4 gai_strerror getaddrinfo gettimeofday inet_pton inet_ntop select socket strerror strlcpy]) +AC_CHECK_FUNCS([accept4 gai_strerror getaddrinfo gettimeofday select socket strerror strlcpy]) # Required for MinGW with GCC v4.8.1 on Win7 AC_DEFINE(WINVER, 0x0501, _) @@ -129,12 +141,19 @@ AC_TYPE_UINT32_T AC_TYPE_UINT8_T if test "$os_cygwin" = "false"; then + AC_CHECK_HEADERS([windows.h], HAVE_WINDOWS_H=yes) + # Required for getaddrinfo (TCP IP - IPv6) AC_CHECK_HEADERS([winsock2.h], HAVE_WINSOCK2_H=yes) if test "x$HAVE_WINSOCK2_H" = "xyes"; then LIBS="$LIBS -lws2_32" - AC_SUBST(LIBS) + AC_SUBST(LIBS) fi + + dnl Can bring inet_ntop()/inet_pton()... or not, depending on distro + dnl (e.g. mingw "native" with MSYS2 or cross-built from Linux); that + dnl is further checked below: + AC_CHECK_HEADERS([ws2tcpip.h], HAVE_WS2TCPIP_H=yes) fi if test "$os_sunos" = "true"; then @@ -153,7 +172,99 @@ WARNING_CFLAGS="-Wall \ -Wsign-compare -Wchar-subscripts \ -Wstrict-prototypes -Wshadow \ -Wformat-security" -AC_SUBST([WARNING_CFLAGS]) + +dnl FIXME: define more thoroughly if C++ code ever appears here +WARNING_CXXFLAGS="$WARNING_CFLAGS" + +dnl Adapted from NUT v2.8.2 configure.ac : +myCFLAGS="$CFLAGS" +AS_IF([test "${GCC}" = "yes"], + [CFLAGS="$myCFLAGS -Werror -Werror=implicit-function-declaration"], + [dnl # Don't know what to complain about for unknown compilers + dnl # FIXME: We presume here they have at least a "-Werror" option + CFLAGS="$myCFLAGS -Werror" + ]) + +AC_CACHE_CHECK([for inet_ntop() with IPv4 and IPv6 support], + [ac_cv_func_inet_ntop], + [AC_LANG_PUSH([C]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[ +#if HAVE_WINDOWS_H +# undef inline +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# if HAVE_WINSOCK2_H +# include +# endif +# if HAVE_WS2TCPIP_H +# include +# endif +#else +# include +#endif +#include +]], + [[/* const char* inet_ntop(int af, const void* src, char* dst, size_t cnt); */ +char buf[128]; +printf("%s", inet_ntop(AF_INET, "1.2.3.4", buf, 10)); +printf("%s", inet_ntop(AF_INET6, "::1", buf, 10)) +/* autoconf adds ";return 0;" */ +]])], + [ac_cv_func_inet_ntop=yes], [ac_cv_func_inet_ntop=no] + ) + AC_LANG_POP([C]) +]) +AS_IF([test x"${ac_cv_func_inet_ntop}" = xyes], + [AC_DEFINE([HAVE_INET_NTOP], 1, [defined if system has the inet_ntop() method])], + [AC_MSG_WARN([Required C library routine inet_ntop() not found]) + AS_IF([test "${os_win32}" = "true"], [AC_MSG_WARN([Windows antivirus might block this test])]) + ] +) + +AC_CACHE_CHECK([for inet_pton() with IPv4 and IPv6 support], + [ac_cv_func_inet_pton], + [AC_LANG_PUSH([C]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[ +#if HAVE_WINDOWS_H +# undef inline +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# if HAVE_WINSOCK2_H +# include +# endif +# if HAVE_WS2TCPIP_H +# include +# endif +#else +# include +#endif +#include +]], + [[/* int inet_pton(int af, const char *src, char *dst); */ +struct in_addr ipv4; +struct in6_addr ipv6; +printf("%i ", inet_pton(AF_INET, "1.2.3.4", &ipv4)); +printf("%i ", inet_pton(AF_INET6, "::1", &ipv6)) +/* autoconf adds ";return 0;" */ +]])], + [ac_cv_func_inet_pton=yes], [ac_cv_func_inet_pton=no] + ) + AC_LANG_POP([C]) +]) +AS_IF([test x"${ac_cv_func_inet_pton}" = xyes], + [AC_DEFINE([HAVE_INET_PTON], 1, [defined if system has the inet_pton() method])], + [AC_MSG_WARN([Required C library routine inet_pton() not found]) + AS_IF([test "${os_win32}" = "true"], [AC_MSG_WARN([Windows antivirus might block this test])]) + ] +) +CFLAGS="$myCFLAGS" + # Check for libusb-1.0 AC_ARG_WITH([libusb], @@ -221,6 +332,22 @@ AS_IF([test "x$enable_debug" = "xyes"], [ CXXFLAGS="-O2" ]) +dnl NOTE: Do not pass these among C(XX)FLAGS to the configure script itself, +dnl they can break common tests unexpectedly. Variants below should work for +dnl GCC and CLANG, and other compilers that emulate them in terms of CLI API. +AC_ARG_ENABLE([Werror], + [AS_HELP_STRING([--enable-Werror], + [Enable compilation failure on warnings (default is no)])], + [enable_Werror=$enableval], + [enable_Werror=no]) +AS_IF([test "x$enable_Werror" = "xyes"], [ + WARNING_CFLAGS="$WARNING_CFLAGS -Werror" + WARNING_CXXFLAGS="$WARNING_CXXFLAGS -Werror" +]) + +AC_SUBST([WARNING_CFLAGS]) +AC_SUBST([WARNING_CXXFLAGS]) + AC_OUTPUT AC_MSG_RESULT([ $PACKAGE $VERSION diff --git a/src/modbus-tcp.c b/src/modbus-tcp.c index 8e5e37cd..1084fc8b 100644 --- a/src/modbus-tcp.c +++ b/src/modbus-tcp.c @@ -68,6 +68,217 @@ #include "modbus-tcp.h" #ifdef OS_WIN32 + +// inet_ntop() and inet_pton() fallbacks picked up from NUT v2.8.2 common/wincompat.c: +#if !HAVE_INET_NTOP +static const char *inet_ntop(int af, const void *src, char *dst, size_t cnt) +{ + /* Instead of WSAAddressToString() consider getnameinfo() if this would in fact + * return decorated addresses (brackets, ports...) as discussed below: + * https://users.ipv6.narkive.com/RXpR5aML/windows-and-inet-ntop-vs-wsaaddresstostring + * https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getnameinfo + * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaaddresstostringa + */ + switch (af) { + case AF_INET: { + struct sockaddr_in srcaddr; + memset(&srcaddr, 0, sizeof(struct sockaddr_in)); + memcpy(&(srcaddr.sin_addr), src, sizeof(srcaddr.sin_addr)); + srcaddr.sin_family = af; + if (WSAAddressToString((struct sockaddr *) &srcaddr, + sizeof(struct sockaddr_in), + 0, + dst, + (LPDWORD) &cnt) != 0) { + WSAGetLastError(); + return NULL; + } + } break; + + case AF_INET6: + /* NOTE: Since WinXP SP1, with IPv6 installed on the system */ + { + struct sockaddr_in6 srcaddr; + memset(&srcaddr, 0, sizeof(struct sockaddr_in6)); + memcpy(&(srcaddr.sin6_addr), src, sizeof(srcaddr.sin6_addr)); + srcaddr.sin6_family = af; + if (WSAAddressToString((struct sockaddr *) &srcaddr, + sizeof(struct sockaddr_in6), + 0, + dst, + (LPDWORD) &cnt) != 0) { + WSAGetLastError(); + return NULL; + } + } + break; + + default: + errno = EAFNOSUPPORT; + return NULL; + } /* switch */ + + return dst; +} +#endif /* !HAVE_INET_NTOP */ + +#if !HAVE_INET_PTON +/* Fallback implementation of inet_pton() for systems that lack it, + * such as older versions of Windows (including MinGW builds that do + * not specifically target _WIN32_WINNT or newer). + * + * Based on code attributed to Paul Vixie, 1996, + * sourced from https://stackoverflow.com/a/15370175/4715872 + */ + +#define NS_INADDRSZ sizeof(struct in_addr) /* 4 */ +#define NS_IN6ADDRSZ sizeof(struct in6_addr) /* 16 */ +#define NS_INT16SZ sizeof(uint16_t) /* 2 */ + +static int inet_pton4(const char *src, void *dst) +{ + uint8_t tmp[NS_INADDRSZ], *tp; /* for struct in_addr *dst */ + + int saw_digit = 0; + int octets = 0; + int ch; + + *(tp = tmp) = 0; + + while ((ch = *src++) != '\0') { + if (ch >= '0' && ch <= '9') { + uint32_t n = *tp * 10 + (ch - '0'); + + if (saw_digit && *tp == 0) + return 0; + + if (n > 255) + return 0; + + *tp = n; + if (!saw_digit) { + if (++octets > 4) + return 0; + saw_digit = 1; + } + } else if (ch == '.' && saw_digit) { + if (octets == 4) + return 0; + *++tp = 0; + saw_digit = 0; + } else + return 0; + } + if (octets < 4) + return 0; + + memcpy(dst, tmp, NS_INADDRSZ); + + return 1; +} + +static int inet_pton6(const char *src, void *dst) +{ + static const char xdigits[] = "0123456789abcdef"; + uint8_t tmp[NS_IN6ADDRSZ]; /* for struct in6_addr *dst */ + + uint8_t *tp = (uint8_t *) memset(tmp, '\0', NS_IN6ADDRSZ); + uint8_t *endp = tp + NS_IN6ADDRSZ; + uint8_t *colonp = NULL; + + const char *curtok = NULL; + int saw_xdigit = 0; + uint32_t val = 0; + int ch; + + /* Leading :: requires some special handling. */ + if (*src == ':') { + if (*++src != ':') + return 0; + } + + curtok = src; + + while ((ch = tolower(*src++)) != '\0') { + const char *pch = strchr(xdigits, ch); + if (pch != NULL) { + val <<= 4; + val |= (pch - xdigits); + if (val > 0xffff) + return 0; + saw_xdigit = 1; + continue; + } + if (ch == ':') { + curtok = src; + if (!saw_xdigit) { + if (colonp) + return 0; + colonp = tp; + continue; + } else if (*src == '\0') { + return 0; + } + if (tp + NS_INT16SZ > endp) + return 0; + *tp++ = (uint8_t) (val >> 8) & 0xff; + *tp++ = (uint8_t) val & 0xff; + saw_xdigit = 0; + val = 0; + continue; + } + if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) && + inet_pton4(curtok, (char *) tp) > 0) { + tp += NS_INADDRSZ; + saw_xdigit = 0; + break; /* '\0' was seen by inet_pton4(). */ + } + return 0; + } + if (saw_xdigit) { + if (tp + NS_INT16SZ > endp) + return 0; + *tp++ = (uint8_t) (val >> 8) & 0xff; + *tp++ = (uint8_t) val & 0xff; + } + if (colonp != NULL) { + /* + * Since some memmove()'s erroneously fail to handle + * overlapping regions, we'll do the shift by hand. + */ + const int n = tp - colonp; + int i; + + if (tp == endp) + return 0; + + for (i = 1; i <= n; i++) { + endp[-i] = colonp[n - i]; + colonp[n - i] = 0; + } + tp = endp; + } + if (tp != endp) + return 0; + + memcpy(dst, tmp, NS_IN6ADDRSZ); + + return 1; +} + +static int inet_pton(int af, const char *src, void *dst) +{ + switch (af) { + case AF_INET: + return inet_pton4(src, dst); + case AF_INET6: + return inet_pton6(src, dst); + default: + return -1; + } +} +#endif /* !HAVE_INET_PTON */ + static int _modbus_tcp_init_win32(void) { /* Initialise Windows Socket API */ @@ -236,7 +447,7 @@ static int _modbus_tcp_set_ipv4_options(int s) /* Set the TCP no delay flag */ /* SOL_TCP = IPPROTO_TCP */ option = 1; - rc = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &option, sizeof(int)); + rc = setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (const void *)&option, sizeof(option)); if (rc == -1) { return -1; } @@ -264,7 +475,7 @@ static int _modbus_tcp_set_ipv4_options(int s) **/ /* Set the IP low delay option */ option = IPTOS_LOWDELAY; - rc = setsockopt(s, IPPROTO_IP, IP_TOS, &option, sizeof(int)); + rc = setsockopt(s, IPPROTO_IP, IP_TOS, (const void *)&option, sizeof(option)); if (rc == -1) { return -1; } @@ -547,6 +758,8 @@ int modbus_tcp_listen(modbus_t *ctx, int nb_connection) #ifdef OS_WIN32 if (_modbus_tcp_init_win32() == -1) { + if (ctx->debug) + perror("_modbus_tcp_init_win32"); return -1; } #endif @@ -559,11 +772,15 @@ int modbus_tcp_listen(modbus_t *ctx, int nb_connection) new_s = socket(PF_INET, flags, IPPROTO_TCP); if (new_s == -1) { + if (ctx->debug) + perror("socket"); return -1; } enable = 1; - if (setsockopt(new_s, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) == -1) { + if (setsockopt(new_s, SOL_SOCKET, SO_REUSEADDR, (const void *)&enable, sizeof(enable)) == -1) { + if (ctx->debug) + perror("setsockopt"); close(new_s); return -1; } @@ -588,11 +805,15 @@ int modbus_tcp_listen(modbus_t *ctx, int nb_connection) } if (bind(new_s, (struct sockaddr *) &addr, sizeof(addr)) == -1) { + if (ctx->debug) + perror("bind"); close(new_s); return -1; } if (listen(new_s, nb_connection) == -1) { + if (ctx->debug) + perror("listen"); close(new_s); return -1; } @@ -680,7 +901,7 @@ int modbus_tcp_pi_listen(modbus_t *ctx, int nb_connection) continue; } else { int enable = 1; - rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); + rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const void *)&enable, sizeof(enable)); if (rc != 0) { close(s); if (ctx->debug) { diff --git a/src/modbus.c b/src/modbus.c index 8ffbaee7..b735359e 100644 --- a/src/modbus.c +++ b/src/modbus.c @@ -421,7 +421,7 @@ int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) while (length_to_read != 0) { rc = ctx->backend->select(ctx, &rset, p_tv, length_to_read); if (rc == -1) { - _error_print(ctx, "select"); + _error_print(ctx, "ctx->backend->select"); if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) { #ifdef _WIN32 wsa_err = WSAGetLastError(); @@ -488,6 +488,7 @@ int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type) int i; for (i = 0; i < rc; i++) printf("<%.2X>", msg[msg_length + i]); + printf("\n"); } /* Sums bytes received */ @@ -767,7 +768,9 @@ static int response_exception(modbus_t *ctx, va_list ap; va_start(ap, template); + fflush(stderr); vfprintf(stderr, template, ap); + fflush(stderr); va_end(ap); }