diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..1cf2ea6 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Jan Friesse diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..65ad972 --- /dev/null +++ b/COPYING @@ -0,0 +1,13 @@ +Copyright (c) 2010-2011, Red Hat, Inc. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE +FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c80313b --- /dev/null +++ b/Makefile @@ -0,0 +1,98 @@ +# Copyright (c) 2010-2011, Red Hat, Inc. +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE +# FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +# OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +CFLAGS += -Wall -Wshadow -Wp,-D_FORTIFY_SOURCE=2 -g +PREFIX ?= /usr/local +BINDIR ?= $(PREFIX)/bin +MANDIR ?= $(PREFIX)/share/man + +INSTALL_PROGRAM ?= install + +PROGRAM_NAME = omping +VERSION_SH = `grep PROGRAM_VERSION omping.h | head -n 1 | sed 's/^.*\"\(.*\)\"/\1/'` + +all: $(PROGRAM_NAME) + +all-illumos: + CFLAGS="$(CFLAGS) -D_XOPEN_SOURCE=600 -D_XOPEN_SOURCE_EXTENDED=1 -D__EXTENSIONS__=1" \ + LDFLAGS="$(LDFLAGS) -lsocket -lnsl" $(MAKE) all + +$(PROGRAM_NAME): addrfunc.o cli.o gcra.o logging.o msg.o msgsend.o omping.o rhfunc.o rsfunc.o \ + sockfunc.o tlv.o util.o + $(CC) $(CFLAGS) $(LDFLAGS) addrfunc.o cli.o gcra.o logging.o msg.o msgsend.o omping.o \ + rhfunc.o rsfunc.o sockfunc.o tlv.o util.o -o $@ + +addrfunc.o: addrfunc.c addrfunc.h logging.h + $(CC) -c $(CFLAGS) $< -o $@ + +cli.o: cli.c cli.h addrfunc.h omping.h logging.h sockfunc.h + $(CC) -c $(CFLAGS) $< -o $@ + +gcra.o: gcra.c gcra.h util.h + $(CC) -c $(CFLAGS) $< -o $@ + +logging.o: logging.c logging.h + $(CC) -c $(CFLAGS) $< -o $@ + +msg.o: msg.c msg.h logging.h omping.h tlv.h + $(CC) -c $(CFLAGS) $< -o $@ + +msgsend.o: msgsend.c addrfunc.h logging.h msg.h msgsend.h omping.h rsfunc.h util.h + $(CC) -c $(CFLAGS) $< -o $@ + +omping.o: omping.c addrfunc.h cli.h logging.h msg.h msgsend.h omping.h rhfunc.h rsfunc.h sockfunc.h tlv.h util.h + $(CC) -c $(CFLAGS) $< -o $@ + +rhfunc.o: rhfunc.c rhfunc.h addrfunc.h util.h + $(CC) -c $(CFLAGS) $< -o $@ + +rsfunc.o: rsfunc.c rsfunc.h addrfunc.h logging.h util.h + $(CC) -c $(CFLAGS) $< -o $@ + +sockfunc.o: sockfunc.c addrfunc.h logging.h sockfunc.h + $(CC) -c $(CFLAGS) $< -o $@ + +tlv.o: tlv.c logging.h addrfunc.h util.h + $(CC) -c $(CFLAGS) $< -o $@ + +util.o: util.c util.h logging.h + $(CC) -c $(CFLAGS) $< -o $@ + +install: $(PROGRAM_NAME) + test -z "$(DESTDIR)/$(BINDIR)" || mkdir -p "$(DESTDIR)/$(BINDIR)" + $(INSTALL_PROGRAM) -c $< $(DESTDIR)/$(BINDIR) + test -z "$(DESTDIR)/$(MANDIR)/man8" || mkdir -p "$(DESTDIR)/$(MANDIR)/man8" + $(INSTALL_PROGRAM) -c -m 0644 $<.8 $(DESTDIR)/$(MANDIR)/man8 + +uninstall: + rm -f $(DESTDIR)/$(BINDIR)/$(PROGRAM_NAME) + rm -f $(DESTDIR)/$(MANDIR)/man8/$(PROGRAM_NAME).8 + +install-strip: + $(MAKE) INSTALL_PROGRAM="$(INSTALL_PROGRAM) -s" install + +TAGS: + ctags *.[ch] + +dist: + mkdir -p $(PROGRAM_NAME)-$(VERSION_SH) + cp AUTHORS COPYING Makefile *.[ch] $(PROGRAM_NAME).8 $(PROGRAM_NAME).spec $(PROGRAM_NAME)-$(VERSION_SH)/ + tar -czf $(PROGRAM_NAME)-$(VERSION_SH).tar.gz $(PROGRAM_NAME)-$(VERSION_SH) + rm -rf $(PROGRAM_NAME)-$(VERSION_SH) + +installdirs: + mkdir -p "$(DESTDIR)/bin" + +clean: + rm -f $(PROGRAM_NAME) *.o diff --git a/addrfunc.c b/addrfunc.c new file mode 100644 index 0000000..2ec93df --- /dev/null +++ b/addrfunc.c @@ -0,0 +1,737 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "addrfunc.h" +#include "logging.h" + +/* + * Compares two addrinfo structures. Family, socktype, protocol and sockaddr are + * compared. This one don't goes to deep, so compares really only one struct not + * list of them. + */ +int +af_ai_eq(const struct addrinfo *a1, const struct addrinfo *a2) +{ + + return ((a1->ai_family == a2->ai_family) && + (a1->ai_socktype == a2->ai_socktype) && + (a1->ai_protocol == a2->ai_protocol) && + af_sockaddr_eq(a1->ai_addr, a2->ai_addr)); +} + +/* + * Deep compare of two addrinfo structures. Internally calls addrinfo_eq + * function to compare one struct. It returns 1, if at least one addr from a1 + * matches with at least on addr from a2. + */ +int +af_ai_deep_eq(const struct addrinfo *a1, const struct addrinfo *a2) +{ + const struct addrinfo *a1_i, *a2_i; + + for (a1_i = a1; a1_i != NULL; a1_i = a1_i->ai_next) { + for (a2_i = a2; a2_i != NULL; a2_i = a2_i->ai_next) { + if (af_ai_eq(a1_i, a2_i)) { + return (1); + } + } + } + + return (0); +} + +/* + * Find out if ai is duplicate of items in ai_list. ai_list is first addrinfo item (returned by + * getaddrinfo) and list is traversed up to ai item. + * Function return 0 if ai is not duplicate of items in ai_list, otherwise 1 + */ +int +af_ai_is_dup(const struct addrinfo *ai_list, const struct addrinfo *ai) +{ + const struct addrinfo *ai_i; + int res; + + res = 0; + + for (ai_i = ai_list; res == 0 && ai_i != ai && ai_i != NULL; ai_i = ai_i->ai_next) { + if (af_ai_eq(ai_i, ai)) { + res = 1; + } + } + + return (res); +} + +/* + * Test if given list of addrinfo ai is loopback address or not. Returns > 0 if + * addrinfo list ai is loopback, otherwise 0. This one goes to deep. + */ +int +af_ai_deep_is_loopback(const struct addrinfo *a1) +{ + const struct addrinfo *a1_i; + + for (a1_i = a1; a1_i != NULL; a1_i = a1_i->ai_next) { + if (af_ai_is_loopback(a1_i)) { + return (1); + } + } + + return (0); +} + +/* Deeply test what IP versions are supported on given ai_addr. Can return 4 (only ipv4 is + * supported), 6 (only ipv6 is supported), 0 (both ipv4 and ipv6 are supported) and -1 (nether ipv4 + * or ipv6 are supported) + */ +int +af_ai_deep_supported_ipv(const struct addrinfo *ai_addr) +{ + const struct addrinfo *ai_iter; + int ip4, ip6; + + ip4 = 0; + ip6 = 0; + + for (ai_iter = ai_addr; ai_iter != NULL; ai_iter = ai_iter->ai_next) { + switch (af_ai_supported_ipv(ai_iter)) { + case 4: + ip4 = 1; + break; + case 6: + ip6 = 1; + break; + case 0: + DEBUG_PRINTF("internal program error."); + err(1, "Internal program error"); + break; + } + } + + if (ip4 && ip6) + return (0); + if (ip6) + return (6); + if (ip4) + return (4); + + return (-1); +} + +/* + * Test if given addrinfo ai is loopback address or not. Returns > 0 if + * addrinfo ai is loopback, otherwise 0. This one don't goes to deep, + * so compares really only one struct not list of them. + */ +int +af_ai_is_loopback(const struct addrinfo *ai) +{ + int res; + + switch (ai->ai_family) { + case PF_INET: + res = ntohl(((struct sockaddr_in *)(ai->ai_addr))->sin_addr.s_addr) >> 24 == 0x7f; + break; + case PF_INET6: + res = IN6_IS_ADDR_LOOPBACK(&((struct sockaddr_in6 *)(ai->ai_addr))->sin6_addr); + break; + default: + DEBUG_PRINTF("Unknown ai family %d", ai->ai_family); + errx(1, "Unknown ai family %d", ai->ai_family); + } + + return (res); +} + +/* + * Free content of ai_list. List must have sas field active (not ai field) + */ +void +af_ai_list_free(struct ai_list *ai_list) +{ + struct ai_item *ai_item; + struct ai_item *ai_item_next; + + ai_item = TAILQ_FIRST(ai_list); + + while (ai_item != NULL) { + ai_item_next = TAILQ_NEXT(ai_item, entries); + + free(ai_item->host_name); + free(ai_item); + + ai_item = ai_item_next; + } + + TAILQ_INIT(ai_list); +} + +/* Return supported ip version. This function doesn't go deeply to structure. It can return 4 (ipv4 + * is supported), 6 (ipv6 is supported) or 0 (nether ipv4 or ipv6 are supported). + */ +int +af_ai_supported_ipv(const struct addrinfo *ai_addr) +{ + int ipv; + + ipv = 0; + + switch (ai_addr->ai_family) { + case PF_INET: + ipv = 4; + break; + case PF_INET6: + ipv = 6; + break; + } + + return (ipv); +} + +/* + * Make result address from two addresses a1 and a2. addr_source is primary source of address (for + * ipv6 also scope, ...) and can be 1 or 2. port_source address is for copy of port number. Result + * is stored in res. + * Function can return -1 on fail (addr_source or port_number is not 1 or 2), otherwise 0. + */ +int +af_copy_addr(const struct sockaddr_storage *a1, const struct sockaddr_storage *a2, int addr_source, + int port_source, struct sockaddr_storage *res) +{ + const struct sockaddr_storage *sas; + + if (addr_source != 1 && addr_source != 2) { + return (-1); + } + + if (port_source != 1 && port_source != 2) { + return (-1); + } + + sas = (addr_source == 1 ? a1 : a2); + memcpy(res, sas, sizeof(struct sockaddr_storage)); + + if (addr_source == port_source) { + return (0); + } + + sas = (port_source == 1 ? a1 : a2); + + switch (sas->ss_family) { + case AF_INET: + ((struct sockaddr_in *)res)->sin_port = ((struct sockaddr_in *)sas)->sin_port; + break; + case AF_INET6: + ((struct sockaddr_in6 *)res)->sin6_port = ((struct sockaddr_in6 *)sas)->sin6_port; + break; + default: + DEBUG_PRINTF("Unknown sas family %d", sas->ss_family); + errx(1, "Unknown sas family %d", sas->ss_family); + + } + + return (0); +} + +/* + * Copy informations stored in sockaddr sa to sockaddr_storage sas. + */ +void +af_copy_sa_to_sas(struct sockaddr_storage *sas, const struct sockaddr *sa) +{ + + memset(sas, 0, sizeof(*sa)); + memcpy(sas, sa, af_sa_len(sa)); +} + +/* + * Fill in sockaddr sa pointer with in addr any for specified sa_family with specified port. port + * must be in local byte order. + */ +void +af_create_any_addr(struct sockaddr *sa, int sa_family, uint16_t port) +{ + struct sockaddr_in sa_in; + struct sockaddr_in6 sa_in6; + + switch (sa_family) { + case PF_INET: + memset(&sa_in, 0, sizeof(sa_in)); + sa_in.sin_family = sa_family; + sa_in.sin_port = htons(port); + sa_in.sin_addr.s_addr = INADDR_ANY; + memcpy(sa, &sa_in, sizeof(sa_in)); + break; + case PF_INET6: + memset(&sa_in6, 0, sizeof(sa_in6)); + sa_in6.sin6_family = sa_family; + sa_in6.sin6_port = htons(port); + sa_in6.sin6_addr = in6addr_any; + memcpy(sa, &sa_in6, sizeof(sa_in6)); + break; + default: + DEBUG_PRINTF("Unknown ai family %d", sa_family); + errx(1, "Unknown ai family %d", sa_family); + } +} + +/* + * Tries to find local address in ai_list with given ip_ver. if_flags may be set to bit mask with + * IFF_MULTICAST and/or IFF_BROADCAST and only network interface with that flags will be accepted. + * Returns 0 on success, otherwise -1. + * It also changes ifa_list (result of getaddrs), ifa_local (local addr) and ai_item (addrinfo item + * which matches ifa_local). + */ +int +af_find_local_ai(const struct ai_list *ai_list, int *ip_ver, struct ifaddrs **ifa_list, + struct ifaddrs **ifa_local, struct ai_item **ai_item, unsigned int if_flags) +{ + struct addrinfo *ai_i; + struct ai_item *aip; + struct ifaddrs *ifa, *ifa_i; + char sa_str[LOGGING_SA_TO_STR_LEN]; + char sa_str2[LOGGING_SA_TO_STR_LEN]; + int ipv4_fallback; + int res; + + *ifa_local = NULL; + ipv4_fallback = 0; + + if (getifaddrs(&ifa) == -1) { + err(1, "getifaddrs"); + } + + TAILQ_FOREACH(aip, ai_list, entries) { + for (ai_i = aip->ai; ai_i != NULL; ai_i = ai_i->ai_next) { + if (af_ai_is_dup(aip->ai, ai_i)) { + logging_sa_to_str(ai_i->ai_addr, sa_str, sizeof(sa_str)); + DEBUG2_PRINTF("Found duplicate addr %s", sa_str); + continue ; + } + + for (ifa_i = ifa; ifa_i != NULL; ifa_i = ifa_i->ifa_next) { + if (ifa_i->ifa_addr == NULL || + (ifa_i->ifa_addr->sa_family != AF_INET && + ifa_i->ifa_addr->sa_family != AF_INET6)) { + continue ; + } + + logging_sa_to_str(ifa_i->ifa_addr, sa_str, sizeof(sa_str)); + logging_sa_to_str(ai_i->ai_addr, sa_str2, sizeof(sa_str2)); + DEBUG2_PRINTF("Comparing %s(%s) with %s", sa_str, ifa_i->ifa_name, + sa_str2); + + if (af_sockaddr_eq(ifa_i->ifa_addr, ai_i->ai_addr)) { + res = af_is_supported_local_ifa(ifa_i, *ip_ver, if_flags); + + if (res == 1 || res == 2) { + if (*ifa_local != NULL && ipv4_fallback == 0) + goto multiple_match_error; + + *ifa_list = ifa; + *ifa_local = ifa_i; + *ai_item = aip; + + if (*ip_ver == 0) { + /* + * Device supports ipv6 + */ + *ip_ver = 6; + DEBUG2_PRINTF("Supports ipv6"); + } + + if (res == 2) { + /* + * Set this item as ipv4 fallback + */ + ipv4_fallback++; + DEBUG2_PRINTF("Supports ipv4 - fallback"); + } + } + } + } + } + } + + if (*ip_ver == 0 && *ifa_local != NULL) { + if (ipv4_fallback > 1) + goto multiple_match_error; + + *ip_ver = 4; + } + + if (*ifa_local != NULL) { + return (0); + } + + DEBUG_PRINTF("Can't find local addr"); + return (-1); + +multiple_match_error: + errx(1, "Multiple local interfaces match parameters."); + return (-1); +} + +/* + * Convert host_name and port with ip ver (4 or 6) to addrinfo. + * Wrapper on getaddrinfo + */ +struct addrinfo * +af_host_to_ai(const char *host_name, const char *port, int ip_ver) +{ + struct addrinfo ai_hints, *ai_res0, *ai_i; + int error; + char ai_s[LOGGING_SA_TO_STR_LEN]; + + memset(&ai_hints, 0, sizeof(ai_hints)); + switch (ip_ver) { + case 0: + ai_hints.ai_family = PF_UNSPEC; + break; + case 4: + ai_hints.ai_family = PF_INET; + break; + case 6: + ai_hints.ai_family = PF_INET6; + break; + default: + errx(1, "Unknown PF Family"); + /* NOTREACHED */ + } + + ai_hints.ai_socktype = SOCK_DGRAM; + ai_hints.ai_protocol = IPPROTO_UDP; + ai_hints.ai_flags = AI_PASSIVE; + + DEBUG_PRINTF("getaddrinfo for \"%s\" port %s ip_ver %d", host_name, + port, ip_ver); + error = getaddrinfo(host_name, port, &ai_hints, &ai_res0); + if (error != 0) { + errx(1, "Can't get addr info for %s: %s", host_name, gai_strerror(error)); + } + + if (logging_get_verbose() >= LOGGING_LEVEL_DEBUG2) { + for (ai_i = ai_res0; ai_i != NULL; ai_i = ai_i->ai_next) { + logging_ai_to_str(ai_i, ai_s, sizeof(ai_s)); + DEBUG2_PRINTF("%s", ai_s); + } + } + + return (ai_res0); +} + +/* + * Test if addrinfo a1 is included in ai_list list. Return 1 if a1 is included, otherwise 0. + */ +int +af_is_ai_in_list(const struct addrinfo *a1, const struct ai_list *ai_list) +{ + struct ai_item *aip; + + TAILQ_FOREACH(aip, ai_list, entries) { + if (af_ai_deep_eq(a1, aip->ai)) + return (1); + } + + return (0); +} + +/* + * Test if addr is multicast address. + * Return 0 if address is not multicast addres, otherwise != 0. + */ +int +af_is_sa_mcast(const struct sockaddr *addr) +{ + + switch (addr->sa_family) { + case AF_INET: + return IN_MULTICAST(ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr)); + break; + case AF_INET6: + return IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6 *)addr)->sin6_addr); + break; + default: + DEBUG_PRINTF("Unknown sockaddr family"); + errx(1, "Unknown sockaddr family"); + break; + } + + return (0); +} + +/* + * Test if ifa is supported device. + * Such device must: + * - not be loopback + * - be up + * - support for if_flags (multicast/broadcast) + * - support given ip_ver + * if_flags may be set to bit mask with IFF_MULTICAST and/or IFF_BROADCAST. + * Function returns 0, if device doesn't fulfill requirements. 1, if device supports all + * requirements and 2, if device support requirements and ip_ver is set to 0 but device supports + * ipv4. + */ +int +af_is_supported_local_ifa(const struct ifaddrs *ifa, int ip_ver, unsigned int if_flags) +{ + char ai_s[LOGGING_SA_TO_STR_LEN]; + + logging_sa_to_str(ifa->ifa_addr, ai_s, sizeof(ai_s)); + + if (ifa->ifa_flags & IFF_LOOPBACK) { + DEBUG2_PRINTF("%s with addr %s is loopback", ifa->ifa_name, ai_s); + + return (0); + } + + if (!(ifa->ifa_flags & IFF_UP)) { + DEBUG2_PRINTF("%s with addr %s is not up", ifa->ifa_name, ai_s); + + return (0); + } + + if (if_flags & IFF_MULTICAST) { + if (!(ifa->ifa_flags & IFF_MULTICAST)) { + DEBUG2_PRINTF("%s with addr %s doesn't support mcast", ifa->ifa_name, ai_s); + + return (0); + } + } + + if (if_flags & IFF_BROADCAST) { + if (!(ifa->ifa_flags & IFF_BROADCAST)) { + DEBUG2_PRINTF("%s with addr %s doesn't support broadcast", ifa->ifa_name, + ai_s); + + return (0); + } + } + + if (ip_ver != 0 && af_sa_supported_ipv(ifa->ifa_addr) != ip_ver) { + DEBUG2_PRINTF("%s doesn't support requested ipv%d", ai_s, ip_ver); + + return (0); + } + + + if (ip_ver == 0 && af_sa_supported_ipv(ifa->ifa_addr) == 4) { + DEBUG2_PRINTF("%s doesn't support ipv6. Saving ipv4 as fallback", ai_s); + + return (2); + } + + DEBUG_PRINTF("Found local addr %s as device %s", ai_s, ifa->ifa_name); + return (1); +} + +/* + * Return length of sockaddr structure. + */ +socklen_t +af_sa_len(const struct sockaddr *sa) +{ + socklen_t res; + + switch (sa->sa_family) { + case AF_INET: + res = sizeof(struct sockaddr_in); + break; + case AF_INET6: + res = sizeof(struct sockaddr_in6); + break; + default: + DEBUG_PRINTF("Internal program error"); + errx(1,"Internal program error"); + break; + } + + return (res); +} + +/* + * Return port number in network order from addr + */ +uint16_t +af_sa_port(const struct sockaddr *addr) +{ + uint16_t port; + + switch (addr->sa_family) { + case AF_INET: + port = (((struct sockaddr_in *)addr)->sin_port); + break; + case AF_INET6: + port = (((struct sockaddr_in6 *)addr)->sin6_port); + break; + default: + DEBUG_PRINTF("Internal program error"); + err(1, "Internal program error"); + break; + } + + return (port); +} + +/* + * Set port number in network order to addr + */ +void +af_sa_set_port(struct sockaddr *addr, uint16_t port) +{ + + switch (addr->sa_family) { + case AF_INET: + ((struct sockaddr_in *)addr)->sin_port = port; + break; + case AF_INET6: + ((struct sockaddr_in6 *)addr)->sin6_port = port; + break; + default: + DEBUG_PRINTF("Internal program error"); + err(1, "Internal program error"); + break; + } +} + +/* + * Return supported ip version. This function doesn't go deeply to structure. It can return 4 (ipv4 + * is supported), 6 (ipv6 is supported) or 0 (nether ipv4 or ipv6 are supported). + */ +int +af_sa_supported_ipv(const struct sockaddr *sa) +{ + int ipv; + + ipv = 0; + + switch (sa->sa_family) { + case AF_INET: + ipv = 4; + break; + case AF_INET6: + ipv = 6; + break; + } + + return (ipv); +} + +/* + * Fill in sockaddr dest pointer with in addr any for family from src and port from src. + */ +void +af_sa_to_any_addr(struct sockaddr *dest, const struct sockaddr *src) +{ + uint16_t port; + + switch (src->sa_family) { + case PF_INET: + port = ntohs(((struct sockaddr_in *)src)->sin_port); + break; + case PF_INET6: + port = ntohs(((struct sockaddr_in6 *)src)->sin6_port); + break; + default: + DEBUG_PRINTF("Unknown ai family %d", src->sa_family); + errx(1, "Unknown ai family %d", src->sa_family); + } + + af_create_any_addr(dest, src->sa_family, port); +} + +/* + * Convert sockaddr address to string. Returned value is dst or NULL on fail. + */ +char * +af_sa_to_str(const struct sockaddr *sa, char dst[INET6_ADDRSTRLEN]) +{ + + dst[0] = 0; + + switch (sa->sa_family) { + case PF_INET: + inet_ntop(sa->sa_family, &((struct sockaddr_in *)(sa))->sin_addr, dst, + INET6_ADDRSTRLEN); + break; + case PF_INET6: + inet_ntop(sa->sa_family, &((struct sockaddr_in6 *)(sa))->sin6_addr, dst, + INET6_ADDRSTRLEN); + break; + default: + return (NULL); + } + + return (dst); +} + +/* + * Return length of sockaddr_storage structure. + */ +socklen_t +af_sas_len(const struct sockaddr_storage *sas) +{ + return (af_sa_len((const struct sockaddr *)sas)); +} + +/* + * Compares two sockaddr structures. Only family and addr is compared. If + * sockaddr differs 0 is returned, otherwise not 0. + */ +int +af_sockaddr_eq(const struct sockaddr *sa1, const struct sockaddr *sa2) +{ + int res; + + res = 0; + + if (sa1->sa_family == sa2->sa_family) { + switch (sa1->sa_family) { + case AF_INET: + res = (((struct sockaddr_in *)sa1)->sin_addr.s_addr == + ((struct sockaddr_in *)sa2)->sin_addr.s_addr); + break; + case AF_INET6: + res = IN6_ARE_ADDR_EQUAL( + &((struct sockaddr_in6 *)sa1)->sin6_addr, + &((struct sockaddr_in6 *)sa2)->sin6_addr); + break; + default: + err(1, "Unknown sockaddr family"); + break; + } + } + + return (res); +} diff --git a/addrfunc.h b/addrfunc.h new file mode 100644 index 0000000..466b0d1 --- /dev/null +++ b/addrfunc.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#ifndef _ADDRFUNC_H_ +#define _ADDRFUNC_H_ + +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Cast s to sockaddr storage pointer. Used mainly with sockaddr_storage + */ +#define AF_CAST_SA(s) ((struct sockaddr *)s) + +/* + * Address info item. This is intended to use with TAILQ list. + */ +struct ai_item { + union { + struct addrinfo *ai; + struct sockaddr_storage sas; + }; + char *host_name; + TAILQ_ENTRY(ai_item) entries; +}; + +/* + * Typedef of TAILQ head of list of ai_item(s) + */ +TAILQ_HEAD(ai_list, ai_item); + +extern int af_ai_eq(const struct addrinfo *a1, const struct addrinfo *a2); +extern int af_ai_deep_eq(const struct addrinfo *a1, const struct addrinfo *a2); +extern int af_ai_deep_is_loopback(const struct addrinfo *a1); +extern int af_ai_deep_supported_ipv(const struct addrinfo *ai_addr); +extern int af_ai_is_dup(const struct addrinfo *ai_list, const struct addrinfo *ai); +extern int af_ai_is_loopback(const struct addrinfo *ai); +extern void af_ai_list_free(struct ai_list *ai_list); +extern int af_ai_supported_ipv(const struct addrinfo *ai_addr); + +extern int af_copy_addr(const struct sockaddr_storage *a1, + const struct sockaddr_storage *a2, int addr_source, int port_source, + struct sockaddr_storage *res); + +extern void af_copy_sa_to_sas(struct sockaddr_storage *sas, + const struct sockaddr *sa); + +extern void af_create_any_addr(struct sockaddr *sa, int sa_family, uint16_t port); + +extern int af_find_local_ai(const struct ai_list *ai_list, int *ip_ver, + struct ifaddrs **ifa_list, struct ifaddrs **ifa_local, struct ai_item **ai_item, + unsigned int if_flags); + +extern struct addrinfo *af_host_to_ai(const char *host_name, const char *port, int ip_ver); +extern int af_is_ai_in_list(const struct addrinfo *a1, const struct ai_list *ai_list); +extern int af_is_sa_mcast(const struct sockaddr *addr); + +extern int af_is_supported_local_ifa(const struct ifaddrs *ifa, int ip_ver, + unsigned int if_flags); + +extern socklen_t af_sa_len(const struct sockaddr *sa); +extern uint16_t af_sa_port(const struct sockaddr *addr); +extern void af_sa_set_port(struct sockaddr *addr, uint16_t port); +extern int af_sa_supported_ipv(const struct sockaddr *sa); +extern void af_sa_to_any_addr(struct sockaddr *dest, const struct sockaddr *src); +extern char *af_sa_to_str(const struct sockaddr *sa, char dst[INET6_ADDRSTRLEN]); +extern socklen_t af_sas_len(const struct sockaddr_storage *sas); +extern int af_sockaddr_eq(const struct sockaddr *sa1, const struct sockaddr *sa2); + +#ifdef __cplusplus +} +#endif + +#endif /* _ADDRFUNC_H_ */ diff --git a/cli.c b/cli.c new file mode 100644 index 0000000..5ca0016 --- /dev/null +++ b/cli.c @@ -0,0 +1,805 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#define __STDC_LIMIT_MACROS + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "addrfunc.h" +#include "omping.h" +#include "cli.h" +#include "logging.h" + +static void conv_list_addrs(struct ai_list *ai_list, int ip_ver); + +static void conv_local_addr(struct ai_list *ai_list, struct ai_item *ai_local, + const struct ifaddrs *ifa_local, int ip_ver, struct ai_item *local_addr, int *single_addr); + +static int conv_params_ipbc(struct ai_item *ipbc_addr, const char *ipbc_addr_s, + const char *port_s, const struct ifaddrs *ifa_local); + +static void conv_params_mcast(int ip_ver, struct ai_item *mcast_addr, const char *mcast_addr_s, + const char *port_s); + +static int parse_remote_addrs(int argc, char * const argv[], const char *port, int ip_ver, + struct ai_list *ai_list); + +static int return_ip_ver(int ip_ver, const char *mcast_addr, const char *port, + struct ai_list *ai_list); + +static void show_version(void); +static void usage(); + +/* + * Parse command line. + * argc and argv are passed from main function. local_ifname will be allocated and filled by name + * of local ethernet interface. ip_ver will be filled by forced ip version or will + * be 0. mcast_addr will be filled by requested mcast address or will be NULL. Port will be filled + * by requested port (string value) or will be NULL. ai_list will be initialized and requested + * hostnames will be stored there. ttl is pointer where user set TTL or default TTL will be stored. + * single_addr is boolean set if only one remote address is entered. quiet is flag for quiet mode. + * cont_stat is flag for enable continuous statistic. timeout_time is number of miliseconds after + * which client exits regardless to number of received/sent packets. wait_for_finish_time is number + * of miliseconds to wait before exit to allow other nodes not to screw up final statistics. + * dup_buf_items is number of items which should be stored in duplicate packet detection buffer. + * Default is MIN_DUP_BUF_ITEMS for intervals > 1, or DUP_BUF_SECS value divided by ping interval + * in seconds or 0, which is used for disabling duplicate detection. rate_limit_time is maximum + * time between two received packets. sndbuf_size is size of socket buffer to allocate for sending + * packets. rcvbuf_size is size of socket buffer to allocate for receiving packets. Both + * sndbuf_size and rcvbuf_size are set to 0 if user doesn't supply option. send_count_queries is by + * default set to 0, but may be overwritten by user and it means that after sending that number of + * queries, client is put to stop state. auto_exit is boolean variable which is enabled by default + * and can be disabled by -E option. If auto_exit is enabled, loop will end if every client is in + * STOP state. + */ +int +cli_parse(struct ai_list *ai_list, int argc, char * const argv[], char **local_ifname, int *ip_ver, + struct ai_item *local_addr, int *wait_time, enum sf_transport_method *transport_method, + struct ai_item *mcast_addr, uint16_t *port, uint8_t *ttl, int *single_addr, int *quiet, + int *cont_stat, int *timeout_time, int *wait_for_finish_time, int *dup_buf_items, + int *rate_limit_time, int *sndbuf_size, int *rcvbuf_size, uint64_t *send_count_queries, + int *auto_exit, enum omping_op_mode *op_mode) +{ + struct ai_item *ai_item; + struct ifaddrs *ifa_list, *ifa_local; + char *ep; + char *mcast_addr_s; + const char *port_s; + double numd; + int ch; + int force; + int num; + int res; + int rate_limit_time_set; + int show_ver; + int wait_for_finish_time_set; + unsigned int ifa_flags; + + *auto_exit = 1; + *cont_stat = 0; + *dup_buf_items = MIN_DUP_BUF_ITEMS; + *ip_ver = 0; + *local_ifname = NULL; + mcast_addr_s = NULL; + *op_mode = OMPING_OP_MODE_NORMAL; + *quiet = 0; + *send_count_queries = 0; + *sndbuf_size = 0; + *single_addr = 0; + *rate_limit_time = 0; + *rcvbuf_size = 0; + *timeout_time = 0; + *ttl = DEFAULT_TTL; + *transport_method = SF_TM_ASM; + *wait_time = DEFAULT_WAIT_TIME; + *wait_for_finish_time = 0; + + force = 0; + ifa_flags = IFF_MULTICAST; + port_s = DEFAULT_PORT_S; + rate_limit_time_set = 0; + show_ver = 0; + wait_for_finish_time_set = 0; + + logging_set_verbose(0); + + while ((ch = getopt(argc, argv, "46CDEFqVvc:i:M:m:O:p:R:r:S:T:t:w:")) != -1) { + switch (ch) { + case '4': + *ip_ver = 4; + break; + case '6': + *ip_ver = 6; + break; + case 'C': + (*cont_stat)++; + break; + case 'D': + *dup_buf_items = 0; + break; + case 'E': + *auto_exit = 0; + break; + case 'F': + force++; + break; + case 'q': + (*quiet)++; + break; + case 'V': + show_ver++; + break; + case 'v': + logging_set_verbose(logging_get_verbose() + 1); + break; + case 'c': + numd = strtod(optarg, &ep); + if (numd < 1 || *ep != '\0' || numd >= ((uint64_t)~0)) { + warnx("illegal number, -c argument -- %s", optarg); + goto error_usage_exit; + } + *send_count_queries= (uint64_t)numd; + break; + case 'i': + numd = strtod(optarg, &ep); + if (numd < 0 || *ep != '\0' || numd * 1000 > INT32_MAX) { + warnx("illegal number, -i argument -- %s", optarg); + goto error_usage_exit; + } + *wait_time = (int)(numd * 1000.0); + break; + case 'M': + if (strcmp(optarg, "asm") == 0) { + *transport_method = SF_TM_ASM; + ifa_flags = IFF_MULTICAST; + } else if (strcmp(optarg, "ssm") == 0 && sf_is_ssm_supported()) { + *transport_method = SF_TM_SSM; + ifa_flags = IFF_MULTICAST; + } else if (strcmp(optarg, "ipbc") == 0 && sf_is_ipbc_supported()) { + *transport_method = SF_TM_IPBC; + ifa_flags = IFF_BROADCAST; + } else { + warnx("illegal parameter, -M argument -- %s", optarg); + goto error_usage_exit; + } + break; + case 'm': + mcast_addr_s = optarg; + break; + case 'O': + if (strcmp(optarg, "normal") == 0) { + *op_mode = OMPING_OP_MODE_NORMAL; + /* + * Temporarily disabled + * + } else if (strcmp(optarg, "server") == 0) { + *op_mode = OMPING_OP_MODE_SERVER; + */ + } else if (strcmp(optarg, "client") == 0) { + *op_mode = OMPING_OP_MODE_CLIENT; + } else { + warnx("illegal parameter, -O argument -- %s", optarg); + goto error_usage_exit; + } + break; + case 'p': + port_s = optarg; + break; + case 'R': + numd = strtod(optarg, &ep); + if (numd < MIN_RCVBUF_SIZE || *ep != '\0' || numd > INT32_MAX) { + warnx("illegal number, -R argument -- %s", optarg); + goto error_usage_exit; + } + *rcvbuf_size = (int)numd; + break; + case 'r': + numd = strtod(optarg, &ep); + if (numd < 0 || *ep != '\0' || numd * 1000 > INT32_MAX) { + warnx("illegal number, -r argument -- %s", optarg); + goto error_usage_exit; + } + *rate_limit_time = (int)(numd * 1000.0); + rate_limit_time_set = 1; + break; + case 'S': + numd = strtod(optarg, &ep); + if (numd < MIN_SNDBUF_SIZE || *ep != '\0' || numd > INT32_MAX) { + warnx("illegal number, -S argument -- %s", optarg); + goto error_usage_exit; + } + *sndbuf_size = (int)numd; + break; + case 't': + num = strtol(optarg, &ep, 10); + if (num <= 0 || num > ((uint8_t)~0) || *ep != '\0') { + warnx("illegal number, -t argument -- %s", optarg); + goto error_usage_exit; + } + *ttl = num; + break; + case 'T': + numd = strtod(optarg, &ep); + if (numd < 0 || *ep != '\0' || numd * 1000 > INT32_MAX) { + warnx("illegal number, -T argument -- %s", optarg); + goto error_usage_exit; + } + *timeout_time = (int)(numd * 1000.0); + break; + case 'w': + numd = strtod(optarg, &ep); + if ((numd < 0 && numd != -1) || *ep != '\0' || numd * 1000 > INT32_MAX) { + warnx("illegal number, -w argument -- %s", optarg); + goto error_usage_exit; + } + wait_for_finish_time_set = 1; + *wait_for_finish_time = (int)(numd * 1000.0); + break; + case '?': + goto error_usage_exit; + /* NOTREACHED */ + break; + + } + } + + argc -= optind; + argv += optind; + + /* + * Param checking + */ + if (show_ver == 1) { + show_version(); + exit(0); + } + + if (show_ver > 1) { + if (*op_mode != OMPING_OP_MODE_NORMAL) { + warnx("op_mode must be set to normal for remote version display."); + goto error_usage_exit; + } + + *op_mode = OMPING_OP_MODE_SHOW_VERSION; + } + + if (force < 1) { + if (*wait_time < DEFAULT_WAIT_TIME) { + warnx("illegal nmber, -i argument %u ms < %u ms. Use -F to force.", + *wait_time, DEFAULT_WAIT_TIME); + goto error_usage_exit; + } + + if (*ttl < DEFAULT_TTL) { + warnx("illegal nmber, -t argument %u < %u. Use -F to force.", + *ttl, DEFAULT_TTL); + goto error_usage_exit; + } + } + + if (force < 2) { + if (*wait_time == 0) { + warnx("illegal nmber, -i argument %u ms < 1 ms. Use -FF to force.", + *wait_time); + goto error_usage_exit; + } + } + + if (*transport_method == SF_TM_IPBC) { + if (*ip_ver == 6) { + warnx("illegal transport method, -M argument ipbc is mutually exclusive " + "with -6 option"); + goto error_usage_exit; + } + + *ip_ver = 4; + } + + /* + * Computed params + */ + if (!wait_for_finish_time_set) { + *wait_for_finish_time = *wait_time * DEFAULT_WFF_TIME_MUL; + if (*wait_for_finish_time < DEFAULT_WAIT_TIME) { + *wait_for_finish_time = DEFAULT_WAIT_TIME; + } + } + + if (*wait_time == 0) { + *dup_buf_items = 0; + } else { + /* + * + 1 is for eliminate trucate errors + */ + *dup_buf_items = ((DUP_BUF_SECS * 1000) / *wait_time) + 1; + + if (*dup_buf_items < MIN_DUP_BUF_ITEMS) { + *dup_buf_items = MIN_DUP_BUF_ITEMS; + } + } + + if (!rate_limit_time_set) { + *rate_limit_time = *wait_time; + + } + + TAILQ_INIT(ai_list); + + parse_remote_addrs(argc, argv, port_s, *ip_ver, ai_list); + *ip_ver = return_ip_ver(*ip_ver, mcast_addr_s, port_s, ai_list); + + if (af_find_local_ai(ai_list, ip_ver, &ifa_list, &ifa_local, &ai_item, ifa_flags) < 0) { + errx(1, "Can't find local address in arguments"); + } + + /* + * Change ai_list to struct of sockaddr_storage(s) + */ + conv_list_addrs(ai_list, *ip_ver); + + /* + * Find local addr and copy that. Also remove that from list + */ + conv_local_addr(ai_list, ai_item, ifa_local, *ip_ver, local_addr, single_addr); + + /* + * Store local ifname + */ + *local_ifname = strdup(ifa_local->ifa_name); + if (*local_ifname == NULL) { + errx(1, "Can't alloc memory"); + } + + switch (*transport_method) { + case SF_TM_ASM: + case SF_TM_SSM: + /* + * Convert mcast addr to something useful + */ + conv_params_mcast(*ip_ver, mcast_addr, mcast_addr_s, port_s); + break; + case SF_TM_IPBC: + /* + * Convert broadcast addr to something useful + */ + res = conv_params_ipbc(mcast_addr, mcast_addr_s, port_s, ifa_local); + if (res == -1) { + warnx("illegal broadcast address, -M argument doesn't match with local" + " broadcast address"); + goto error_usage_exit; + } + break; + } + + /* + * Assign port from mcast_addr + */ + *port = af_sa_port(AF_CAST_SA(&mcast_addr->sas)); + + freeifaddrs(ifa_list); + + return (0); + +error_usage_exit: + usage(); + exit(1); + /* NOTREACHED */ + return (-1); +} + +/* + * Convert list of addrs of addrinfo to list of addrs of sockaddr_storage. This function will also + * correctly free addrinfo(s) in list. + */ +static void +conv_list_addrs(struct ai_list *ai_list, int ip_ver) +{ + struct sockaddr_storage tmp_sas; + struct addrinfo *ai_i; + struct ai_item *ai_item_i; + char *hn; + + TAILQ_FOREACH(ai_item_i, ai_list, entries) { + hn = (char *)malloc(strlen(ai_item_i->host_name) + 1); + if (hn == NULL) { + errx(1, "Can't alloc memory"); + } + + memcpy(hn, ai_item_i->host_name, strlen(ai_item_i->host_name) + 1); + ai_item_i->host_name = hn; + + for (ai_i = ai_item_i->ai; ai_i != NULL; ai_i = ai_i->ai_next) { + if (af_ai_supported_ipv(ai_i) == ip_ver) { + memset(&tmp_sas, 0, sizeof(tmp_sas)); + + memcpy(&tmp_sas, ai_i->ai_addr, ai_i->ai_addrlen); + + freeaddrinfo(ai_item_i->ai); + + memcpy(&ai_item_i->sas, &tmp_sas, sizeof(tmp_sas)); + break; + } + } + } +} + +/* + * Convert ifa_local addr to local_addr. If only one remote_host is entered, single_addr is set, if + * not then ai_local is freed and removed from list. + */ +static void +conv_local_addr(struct ai_list *ai_list, struct ai_item *ai_local, + const struct ifaddrs *ifa_local, int ip_ver, struct ai_item *local_addr, int *single_addr) +{ + size_t addr_len; + uint16_t port; + + switch (ifa_local->ifa_addr->sa_family) { + case AF_INET: + addr_len = sizeof(struct sockaddr_in); + port = ((struct sockaddr_in *)&ai_local->sas)->sin_port; + break; + case AF_INET6: + addr_len = sizeof(struct sockaddr_in6); + port = ((struct sockaddr_in6 *)&ai_local->sas)->sin6_port; + break; + default: + DEBUG_PRINTF("Internal program error"); + err(1, "Internal program error"); + break; + } + + memcpy(&local_addr->sas, ifa_local->ifa_addr, addr_len); + local_addr->host_name = strdup(ai_local->host_name); + if (local_addr->host_name == NULL) { + err(1, "Can't alloc memory"); + /* NOTREACHED */ + } + + switch (ifa_local->ifa_addr->sa_family) { + case AF_INET: + ((struct sockaddr_in *)&local_addr->sas)->sin_port = port; + break; + case AF_INET6: + ((struct sockaddr_in6 *)&local_addr->sas)->sin6_port = port; + break; + default: + DEBUG_PRINTF("Internal program error"); + err(1, "Internal program error"); + break; + } + + *single_addr = (TAILQ_NEXT(TAILQ_FIRST(ai_list), entries) == NULL); + + if (!*single_addr) { + TAILQ_REMOVE(ai_list, ai_local, entries); + + free(ai_local->host_name); + free(ai_local); + } +} + +/* + * Convert ipbc_addr_s to ipbc_addr ai_item. + * Function returns 0 on success, -1 if given broadcast address is not same as local interface one. + */ +static int +conv_params_ipbc(struct ai_item *ipbc_addr, const char *ipbc_addr_s, const char *port_s, + const struct ifaddrs *ifa_local) +{ + struct addrinfo *ai_res, *ai_i; + char ifa_ipbc_addr_s[INET6_ADDRSTRLEN]; + int ip_ver; + + ip_ver = 4; + + if (ifa_local->ifa_broadaddr == NULL) { + errx(1, "selected local interface isn't broadcast aware"); + } + + if (ipbc_addr_s == NULL) { + af_sa_to_str(ifa_local->ifa_broadaddr, ifa_ipbc_addr_s); + ipbc_addr_s = ifa_ipbc_addr_s; + } + + ipbc_addr->host_name = (char *)malloc(strlen(ipbc_addr_s) + 1); + if (ipbc_addr->host_name == NULL) { + errx(1, "Can't alloc memory"); + } + memcpy(ipbc_addr->host_name, ipbc_addr_s, strlen(ipbc_addr_s) + 1); + + ai_res = af_host_to_ai(ipbc_addr_s, port_s, ip_ver); + + for (ai_i = ai_res; ai_i != NULL; ai_i = ai_i->ai_next) { + if (af_ai_supported_ipv(ai_i) == ip_ver) { + memcpy(&ipbc_addr->sas, ai_i->ai_addr, ai_i->ai_addrlen); + break; + } + } + + if (ai_i == NULL) { + DEBUG_PRINTF("Internal program error"); + err(1, "Internal program error"); + } + + freeaddrinfo(ai_res); + + /* + * Test if interface broadcast addr is same as returned broadcast addr + */ + if (!af_sockaddr_eq(ifa_local->ifa_broadaddr, AF_CAST_SA(&ipbc_addr->sas))) { + return (-1); + } + + return (0); +} + +/* + * Convert mcast_addr_s to mcast_addr ai_item + */ +static void +conv_params_mcast(int ip_ver, struct ai_item *mcast_addr, const char *mcast_addr_s, + const char *port_s) +{ + struct addrinfo *ai_res, *ai_i; + + if (mcast_addr_s == NULL) { + switch (ip_ver) { + case 4: + mcast_addr_s = DEFAULT_MCAST4_ADDR; + break; + case 6: + mcast_addr_s = DEFAULT_MCAST6_ADDR; + break; + default: + DEBUG_PRINTF("Internal program error"); + err(1, "Internal program error"); + break; + } + } + + mcast_addr->host_name = (char *)malloc(strlen(mcast_addr_s) + 1); + if (mcast_addr->host_name == NULL) { + errx(1, "Can't alloc memory"); + } + memcpy(mcast_addr->host_name, mcast_addr_s, strlen(mcast_addr_s) + 1); + + ai_res = af_host_to_ai(mcast_addr_s, port_s, ip_ver); + + for (ai_i = ai_res; ai_i != NULL; ai_i = ai_i->ai_next) { + if (af_ai_supported_ipv(ai_i) == ip_ver) { + memcpy(&mcast_addr->sas, ai_i->ai_addr, ai_i->ai_addrlen); + break; + } + } + + if (ai_i == NULL) { + DEBUG_PRINTF("Internal program error"); + err(1, "Internal program error"); + } + + freeaddrinfo(ai_res); + + /* + * Test if addr is really multicast + */ + if (!af_is_sa_mcast(AF_CAST_SA(&mcast_addr->sas))) { + errx(1, "Given address %s is not valid multicast address", mcast_addr_s); + } +} + +/* + * Parse remote addresses. Return list of addresses taken from cli + */ +static int +parse_remote_addrs(int argc, char * const argv[], const char *port, int ip_ver, + struct ai_list *ai_list) +{ + struct addrinfo *ai_res; + struct ai_item *ai_item; + int no_ai; + int i; + + no_ai = 0; + + for (i = 0; i < argc; i++) { + ai_res = af_host_to_ai(argv[i], port, ip_ver); + if (!af_is_ai_in_list(ai_res, ai_list)) { + if (af_ai_deep_is_loopback(ai_res)) { + errx(1,"Address %s looks like loopback. Loopback ping is not " + "supported", argv[i]); + } + + ai_item = (struct ai_item *)malloc(sizeof(struct ai_item)); + if (ai_item == NULL) { + errx(1, "Can't alloc memory"); + } + + memset(ai_item, 0, sizeof(struct ai_item)); + ai_item->ai = ai_res; + ai_item->host_name = argv[i]; + + TAILQ_INSERT_TAIL(ai_list, ai_item, entries); + DEBUG_PRINTF("new address \"%s\" added to list (position %d)", argv[i], + no_ai); + no_ai++; + } else { + freeaddrinfo(ai_res); + } + } + + if (no_ai < 1) { + warnx("at least one remote addresses should be specified"); + usage(); + exit(1); + } + + return (no_ai); +} + +/* + * Return ip version to use. Algorithm is following: + * - If user forced ip version, we will return that one. + * - If user entered mcast addr, we will look, what it supports + * - if only one version is supported, we will return that version + * - otherwise walk addresses and find out, what they support + * - test if every addresses support all versions. + * - If not, test that version for every other addresses + * - if all of them support that version -> return that version + * - if not -> return error + * - otherwise return 0 (item in find_local_addrinfo will be used but preferably ipv6) + */ +static int +return_ip_ver(int ip_ver, const char *mcast_addr, const char *port, struct ai_list *ai_list) +{ + struct addrinfo *ai_res; + struct ai_item *aip; + int mcast_ipver; + int ipver_res, ipver_res2; + + if (ip_ver != 0) { + DEBUG_PRINTF("user forced forced ip_ver is %d, using that", ip_ver); + return (ip_ver); + } + + if (mcast_addr != NULL) { + ai_res = af_host_to_ai(mcast_addr, port, ip_ver); + mcast_ipver = af_ai_deep_supported_ipv(ai_res); + + DEBUG2_PRINTF("mcast_ipver for %s is %d", mcast_addr, mcast_ipver); + + freeaddrinfo(ai_res); + + if (mcast_ipver == -1) { + errx(1, "Mcast address %s doesn't support ipv4 or ipv6", mcast_addr); + } + + if (mcast_ipver != 0) { + DEBUG_PRINTF("mcast address for %s supports only ipv%d, using that", + mcast_addr, mcast_ipver); + + /* + * Walk thru all addresses to find out, what it supports + */ + TAILQ_FOREACH(aip, ai_list, entries) { + ipver_res = af_ai_deep_supported_ipv(aip->ai); + DEBUG2_PRINTF("ipver for %s is %d", aip->host_name, ipver_res); + + if (ipver_res == -1) { + errx(1, "Host %s doesn't support ipv4 or ipv6", + aip->host_name); + } + + if (ipver_res != 0 && ipver_res != mcast_ipver) { + errx(1, "Multicast address is ipv%d but host %s supports" + " only ipv%d", mcast_ipver, aip->host_name, ipver_res); + } + } + + return (mcast_ipver); + } + } + + ipver_res = 0; + + /* + * Walk thru all addresses to find out, what it supports + */ + TAILQ_FOREACH(aip, ai_list, entries) { + ipver_res = af_ai_deep_supported_ipv(aip->ai); + DEBUG2_PRINTF("ipver for %s is %d", aip->host_name, ipver_res); + + if (ipver_res == -1) { + errx(1, "Host %s doesn't support ipv4 or ipv6", aip->host_name); + } + + if (ipver_res != 0) { + break; + } + } + + if (ipver_res == 0) { + /* + * Every address support every version + */ + DEBUG_PRINTF("Every address support all IP versions"); + return (0); + } + + if (ipver_res != 0) { + /* + * Host supports only one version. + * Test availability for that version on all hosts + */ + TAILQ_FOREACH(aip, ai_list, entries) { + ipver_res2 = af_ai_deep_supported_ipv(aip->ai); + DEBUG2_PRINTF("ipver for %s is %d", aip->host_name, ipver_res2); + + if (ipver_res2 == -1) { + errx(1, "Host %s doesn't support ipv4 or ipv6", aip->host_name); + } + + if (ipver_res2 != 0 && ipver_res2 != ipver_res) { + /* + * Host doesn't support ip version of other members + */ + errx(1, "Host %s doesn't support IP version %d", aip->host_name, + ipver_res); + } + } + } + + DEBUG_PRINTF("Every address support ipv%d", ipver_res); + + return (ipver_res); +} + +/* + * Show application version + */ +static void +show_version(void) +{ + + printf("%s version %s\n", PROGRAM_NAME, PROGRAM_VERSION); +} + +/* + * Display application ussage + */ +static void +usage() +{ + + printf("usage: %s [-46CDEFqVv] [-c count] [-i interval] [-M transport_method]\n", + PROGRAM_NAME); + printf("%14s[-m mcast_addr] [-O op_mode] [-p port] [-R rcvbuf] [-r rate_limit]\n", ""); + printf("%14s[-S sndbuf] [-T timeout] [-t ttl] [-w wait_time] remote_addr...\n", ""); +} diff --git a/cli.h b/cli.h new file mode 100644 index 0000000..a642b20 --- /dev/null +++ b/cli.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#ifndef _CLI_H_ +#define _CLI_H_ + +#include "addrfunc.h" +#include "omping.h" +#include "sockfunc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern int cli_parse(struct ai_list *ai_list, int argc, char * const argv[], + char **local_ifname, int *ip_ver, struct ai_item *local_addr, int *wait_time, + enum sf_transport_method *transport_method, struct ai_item *mcast_addr, + uint16_t *port, uint8_t *ttl, int *single_addr, int *quiet, int *cont_stat, + int *timeout_time, int *wait_for_finish_time, int *dup_buf_items, int *rate_limit_time, + int *sndbuf_size, int *rcvbuf_size, uint64_t *send_count_queries, int *auto_exit, + enum omping_op_mode *op_mode); + +#ifdef __cplusplus +} +#endif + +#endif /* _CLI_H_ */ diff --git a/gcra.c b/gcra.c new file mode 100644 index 0000000..3c204e3 --- /dev/null +++ b/gcra.c @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#include + +#include +#include +#include + +#include "gcra.h" +#include "util.h" + +/* + * item is gcra_item to be initialized. Interval is interval in ms in which packet + * will arrive (max), and burst is number of packets which may arrive sooner. + */ +void +gcra_init(struct gcra_item *item, unsigned int interval, unsigned int burst) +{ + + memset(item, 0, sizeof(*item)); + + item->tau = burst * interval; + item->interval = interval; +} + +/* + * item is gcra item and tv is time of packet arrival. + * Returns 0 if packet is non conforming and should be discarded/put to queue, ..., and 1 if packet + * is conforming. + */ +int +gcra_rl(struct gcra_item *item, struct timeval tv) +{ + uint64_t tv_u64; + + tv_u64 = util_tv_to_ms(tv); + + if (item->tat >= item->tau && tv_u64 < item->tat - item->tau) { + return (0); + } else { + item->tat = ((tv_u64 > item->tat) ? tv_u64 : item->tat) + item->interval; + + return (1); + } +} diff --git a/gcra.h b/gcra.h new file mode 100644 index 0000000..d3cfa3f --- /dev/null +++ b/gcra.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#ifndef _GCRA_H_ +#define _GCRA_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Structures definition + */ +struct gcra_item { + uint64_t tat; + unsigned int interval; + unsigned int tau; +}; + +/* + * Prototypes + */ +extern void gcra_init(struct gcra_item *item, unsigned int interval, + unsigned int burst); + +extern int gcra_rl(struct gcra_item *item, struct timeval tv); + +#ifdef __cplusplus +} +#endif + +#endif /* _GCRA_H_ */ diff --git a/logging.c b/logging.c new file mode 100644 index 0000000..3da68d9 --- /dev/null +++ b/logging.c @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#include + +#include + +#include +#include + +#define __STDC_FORMAT_MACROS +#include +#include +#include +#include + +#include "addrfunc.h" +#include "logging.h" + +static int logging_verbose; + +int +logging_ai_to_str(const struct addrinfo *ai, char *dst, int max_l) +{ + if (ai->ai_family == PF_INET || ai->ai_family == PF_INET6) { + return (logging_sa_to_str((struct sockaddr *)ai->ai_addr, dst, max_l)); + } + + return (0); +} + +int +logging_get_verbose(void) +{ + + return (logging_verbose); +} + +int +logging_hexdump(const char *file_name, int line, int log_level, const char *prefix_str, + const void *data, size_t data_len) +{ + size_t i; + int res; + uint8_t u8; + + res = 0; + + if (logging_get_verbose() >= log_level) { + if (logging_get_verbose() >= LOGGING_LEVEL_DEBUG) { + res += fprintf(stderr, "%s:%d ", file_name, line); + } + + if (prefix_str != NULL) { + res += fprintf(stderr, "%s", prefix_str); + } + + for (i = 0; i < data_len; i++) { + u8 = ((const unsigned char *)data)[i]; + + res += fprintf(stderr, "%02"PRIX8, u8); + } + res += fprintf(stderr, "\n"); + } + + return (res); +} + +int +logging_printf(const char *file_name, int line, int log_level, const char *format, ...) +{ + va_list ap; + int res; + + res = 0; + + if (logging_verbose >= log_level) { + va_start(ap, format); + if (logging_verbose >= LOGGING_LEVEL_DEBUG) { + res += fprintf(stderr, "%s:%d ", file_name, line); + } + res += vfprintf(stderr, format, ap); + res += fprintf(stderr, "\n"); + va_end(ap); + } + + return (res); +} + +int +logging_sa_to_str(const struct sockaddr *sa, char *dst, int max_l) +{ + int ipv; + char buf[INET6_ADDRSTRLEN]; + + if (af_sa_to_str(sa, buf) == NULL) { + return (0); + } + + switch (sa->sa_family) { + case PF_INET: + ipv = 4; + break; + case PF_INET6: + ipv = 6; + break; + default: + return (0); + } + + return (snprintf(dst, max_l, "ipv%d, addr: %s", ipv, buf)); +} + +void +logging_set_verbose(int lv) +{ + + logging_verbose = lv; +} diff --git a/logging.h b/logging.h new file mode 100644 index 0000000..b5ca683 --- /dev/null +++ b/logging.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#ifndef _LOGGING_H_ +#define _LOGGING_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + LOGGING_LEVEL_QUIET = 0, + LOGGING_LEVEL_VERBOSE = 1, + LOGGING_LEVEL_DEBUG = 2, + LOGGING_LEVEL_DEBUG2 = 3, +}; + +#define DEBUG_PRINTF(...) \ + logging_printf(__FILE__, __LINE__, LOGGING_LEVEL_DEBUG, __VA_ARGS__) + +#define DEBUG2_HEXDUMP(prefix_str, data, data_len) \ + logging_hexdump(__FILE__, __LINE__, LOGGING_LEVEL_DEBUG2, prefix_str, data, data_len) + +#define DEBUG2_PRINTF(...) \ + logging_printf(__FILE__, __LINE__, LOGGING_LEVEL_DEBUG2, __VA_ARGS__) + +#define VERBOSE_PRINTF(...) \ + logging_printf(__FILE__, __LINE__, LOGGING_LEVEL_VERBOSE, __VA_ARGS__) + +#define LOGGING_SA_TO_STR_LEN (INET6_ADDRSTRLEN + 16) + +extern int logging_ai_to_str(const struct addrinfo *ai, char *dst, int max_l); +extern int logging_get_verbose(void); + +extern int logging_hexdump(const char *file_name, int line, int log_level, + const char *prefix_str, const void *data, size_t data_len); + +extern int logging_printf(const char *file_name, int line, int log_level, + const char *format, ...) __attribute__((__format__(__printf__, 4, 5))); + +extern int logging_sa_to_str(const struct sockaddr *sa, char *dst, int max_l); +extern void logging_set_verbose(int lv); + +#ifdef __cplusplus +} +#endif + +#endif /* _LOGGING_H_ */ diff --git a/msg.c b/msg.c new file mode 100644 index 0000000..b8a372b --- /dev/null +++ b/msg.c @@ -0,0 +1,478 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#include +#include +#include + +#include "logging.h" +#include "msg.h" +#include "omping.h" +#include "tlv.h" +#include "util.h" + +/* + * Create answer message from query message. orig_msg is pointer to buffer with query message + * with orig_msg_len length (only used bytes, not buffer size). new_msg is pointer to buffer where + * to store result message. new_msg_len is size of buffer. ttl is value of TTL option. server_tstamp + * is boolean variable and if set, server timestamp option is added to message. + * + * All options from original messages are copied without changing order. Only exceptions are Server + * Info, Multicast Prefix, Session ID, TTL and Server Timestamp, which are not copied. + * + * Returned value is size of new message or 0 on fail (mostly because msg_len + * is smaller then needed). If success, new message is always at least 1 bytes long. + */ +size_t +msg_answer_create(const char *orig_msg, size_t orig_msg_len, char *new_msg, size_t new_msg_len, + uint8_t ttl, int server_tstamp) +{ + struct tlv_iterator tlv_iter; + enum tlv_opt_type opt_type; + size_t pos; + + pos = 0; + + new_msg[pos++] = (unsigned char)MSG_TYPE_ANSWER; + + memset(&tlv_iter, 0, sizeof(tlv_iter)); + tlv_iter_init(orig_msg, orig_msg_len, &tlv_iter); + + while (tlv_iter_next(&tlv_iter) != -1) { + opt_type = tlv_iter_get_type(&tlv_iter); + if (opt_type != TLV_OPT_TYPE_SERVER_INFO && + opt_type != TLV_OPT_TYPE_MCAST_PREFIX && + opt_type != TLV_OPT_TYPE_SES_ID && + opt_type != TLV_OPT_TYPE_TTL && + opt_type != TLV_OPT_TYPE_SERVER_TSTAMP) { + tlv_iter_item_copy(&tlv_iter, new_msg, new_msg_len, &pos); + } + } + + if (tlv_add_ttl(new_msg, new_msg_len, &pos, ttl) == -1) + goto small_buf_err; + + if (server_tstamp) { + if (tlv_add_server_tstamp(new_msg, new_msg_len, &pos) == -1) + goto small_buf_err; + } + + return (pos); + +small_buf_err: + return (0); +} + +/* + * Decode message. Decoded message is stored in msg_decoded structure. + */ +void +msg_decode(const char *msg, size_t msg_len, struct msg_decoded *decoded) +{ + char debug_str[128]; + struct tlv_iterator tlv_iter; + size_t pos; + uint32_t u32, u32_2; + uint16_t tlv_len; + uint16_t u16; + uint8_t u8; + + memset(decoded, 0, sizeof(struct msg_decoded)); + + decoded->msg_type = (enum msg_type)msg[0]; + + DEBUG2_PRINTF("Message type %c (0x%X)", decoded->msg_type, decoded->msg_type); + + tlv_iter_init(msg, msg_len, &tlv_iter); + + while (tlv_iter_next(&tlv_iter) == 0) { + tlv_len = tlv_iter_get_len(&tlv_iter); + + if (logging_get_verbose() >= LOGGING_LEVEL_DEBUG2) { + snprintf(debug_str, sizeof(debug_str), "%u:%s:%u:", + tlv_iter_get_type(&tlv_iter), + tlv_opt_type_to_str(tlv_iter_get_type(&tlv_iter)), tlv_len); + } + + switch (tlv_iter_get_type(&tlv_iter)) { + case TLV_OPT_TYPE_VERSION: + if (tlv_len == 1) { + memcpy(&u8, tlv_iter_get_data(&tlv_iter), sizeof(u8)); + + decoded->version = u8; + + DEBUG2_PRINTF("%s%u", debug_str, u8); + } else { + DEBUG2_PRINTF("%slen != 1", debug_str); + } + break; + case TLV_OPT_TYPE_CLIENT_ID: + if (tlv_len > 0) { + decoded->client_id_len = tlv_len; + decoded->client_id = tlv_iter_get_data(&tlv_iter); + + DEBUG2_HEXDUMP(debug_str, decoded->client_id, tlv_len); + } else { + DEBUG2_PRINTF("%slen <= 0", debug_str); + } + break; + case TLV_OPT_TYPE_SEQ_NUM: + if (tlv_len == 4) { + decoded->seq_num_isset = 1; + memcpy(&u32, tlv_iter_get_data(&tlv_iter), sizeof(u32)); + u32 = ntohl(u32); + decoded->seq_num = u32; + + DEBUG2_PRINTF("%s%u", debug_str, u32); + } else { + DEBUG2_PRINTF("%slen != 4", debug_str); + } + break; + case TLV_OPT_TYPE_CLIENT_TSTAMP: + if (tlv_len == 8) { + memcpy(&u32, tlv_iter_get_data(&tlv_iter), sizeof(u32)); + u32 = ntohl(u32); + decoded->client_tstamp.tv_sec = u32; + + memcpy(&u32_2, tlv_iter_get_data(&tlv_iter) + sizeof(u32), + sizeof(u32_2)); + u32_2 = ntohl(u32_2); + decoded->client_tstamp.tv_usec = u32_2; + + decoded->client_tstamp_isset = 1; + + DEBUG2_PRINTF("%s(%u,%u)", debug_str, u32, u32_2); + } else { + DEBUG2_PRINTF("%slen != 8", debug_str); + } + break; + case TLV_OPT_TYPE_MCAST_GRP: + if (tlv_len > 2) { + memcpy(&u16, tlv_iter_get_data(&tlv_iter), sizeof(u16)); + u16 = ntohs(u16); + + if ((u16 == AF_IANA_IP && tlv_len == 6) || + (u16 == AF_IANA_IP6 && tlv_len == 18)) { + decoded->mcast_grp_len = tlv_len; + decoded->mcast_grp = tlv_iter_get_data(&tlv_iter); + + DEBUG2_PRINTF("%sAF %u", debug_str, u16); + } else { + DEBUG2_PRINTF("%sbad AF %u or len", debug_str, u16); + } + } else { + DEBUG2_PRINTF("%slen <= 2", debug_str); + } + break; + case TLV_OPT_TYPE_OPT_REQUEST: + if (tlv_len > 1 && (tlv_len % 2 == 0)) { + for (pos = 0; pos < (uint16_t)(tlv_len / 2); pos++) { + memcpy(&u16, tlv_iter_get_data(&tlv_iter) + pos * 2, + sizeof(u16)); + + u16 = ntohs(u16); + + switch (u16) { + case TLV_OPT_TYPE_SERVER_INFO: + decoded->request_opt_server_info = 1; + + DEBUG2_PRINTF("%s%zu opt %u", debug_str, pos, u16); + break; + case TLV_OPT_TYPE_SERVER_TSTAMP: + decoded->request_opt_server_tstamp = 1; + + DEBUG2_PRINTF("%s%zu opt %u", debug_str, pos, u16); + break; + default: + DEBUG2_PRINTF("%s%zu unknown opt %u", debug_str, + pos, u16); + break; + } + } + } else { + DEBUG2_PRINTF("%slen <= 1 || (tlv_len %%2 != 0)", debug_str); + } + break; + case TLV_OPT_TYPE_SERVER_INFO: + if (tlv_len > 0) { + decoded->server_info = tlv_iter_get_data(&tlv_iter); + decoded->server_info_len = tlv_len; + + DEBUG2_HEXDUMP(debug_str, decoded->server_info, tlv_len); + } else { + DEBUG2_PRINTF("%slen <= 0", debug_str); + } + break; + case TLV_OPT_TYPE_TTL: + if (tlv_len == 1) { + memcpy(&u8, tlv_iter_get_data(&tlv_iter), sizeof(u8)); + + decoded->ttl = u8; + + DEBUG2_PRINTF("%s%u", debug_str, u8); + } else { + DEBUG2_PRINTF("%slen != 1", debug_str); + } + break; + case TLV_OPT_TYPE_MCAST_PREFIX: + if (tlv_len > 2) { + memcpy(&u16, tlv_iter_get_data(&tlv_iter), sizeof(u16)); + u16 = ntohs(u16); + + if (u16 == AF_IANA_IP || u16 == AF_IANA_IP6) { + decoded->mcast_prefix_isset = 1; + + DEBUG2_PRINTF("%sAF %u", debug_str, u16); + } else { + DEBUG2_PRINTF("%sbad AF %u", debug_str, u16); + } + } else { + DEBUG2_PRINTF("%slen <= 2", debug_str); + } + break; + case TLV_OPT_TYPE_SES_ID: + if (tlv_len > 0) { + decoded->ses_id_len = tlv_len; + decoded->ses_id = tlv_iter_get_data(&tlv_iter); + + DEBUG2_HEXDUMP(debug_str, decoded->ses_id, tlv_len); + } else { + DEBUG2_PRINTF("%slen <= 0", debug_str); + } + break; + case TLV_OPT_TYPE_SERVER_TSTAMP: + if (tlv_len == 8) { + memcpy(&u32, tlv_iter_get_data(&tlv_iter), sizeof(u32)); + u32 = ntohl(u32); + decoded->server_tstamp.tv_sec = u32; + + memcpy(&u32_2, tlv_iter_get_data(&tlv_iter) + sizeof(u32), + sizeof(u32_2)); + u32_2 = ntohl(u32_2); + decoded->server_tstamp.tv_usec = u32_2; + + decoded->server_tstamp_isset = 1; + + DEBUG2_PRINTF("%s(%u,%u)", debug_str, u32, u32_2); + } else { + DEBUG2_PRINTF("%slen != 8", debug_str); + } + break; + default: + DEBUG2_PRINTF("%s", debug_str); + break; + } + } +} + +/* + * Create init message. msg is pointer to buffer where to store result message. msg_len is size of + * buffer. mcast_addr is required multicast address. client_id is client ID to store in message with + * length client_id_len. + * + * Returned value is size of new message or 0 on fail (mostly because msg_len + * is smaller then needed). If success, new message is always at least 1 bytes long. + */ +size_t +msg_init_create(char *msg, size_t msg_len, int req_si, const struct sockaddr_storage *mcast_addr, + const char *client_id, size_t client_id_len) +{ + size_t pos; + uint16_t u16; + + pos = 0; + + if (client_id == NULL) { + return (0); + } + + msg[pos++] = (unsigned char)MSG_TYPE_INIT; + + if (tlv_add_version(msg, msg_len, &pos) == -1) + goto small_buf_err; + + if (tlv_add(msg, msg_len, &pos, TLV_OPT_TYPE_CLIENT_ID, client_id_len, client_id) == -1) + goto small_buf_err; + + + if (req_si) { + u16 = TLV_OPT_TYPE_SERVER_INFO; + + if (tlv_add_opt_request(msg, msg_len, &pos, &u16, 1) == -1) + goto small_buf_err; + } + + + if (tlv_add_mcast_prefix(msg, msg_len, &pos, mcast_addr) == -1) + goto small_buf_err; + + return (pos); + +small_buf_err: + return (0); +} + +/* + * Create query message. msg is pointer to buffer where to store result message. msg_len is size + * of buffer. mcast_addr is required multicast group address. server_tstamp is boolean to decide if + * to include Option request option with server time stamp. client_id is Client ID with length + * client_id_len. session_id with session_id_len is similar, but for Session ID. + * + * Returned value is size of new message or 0 on fail (mostly because msg_len + * is smaller then needed). If success, new message is always at least 1 bytes long. + */ +size_t +msg_query_create(char *msg, size_t msg_len, const struct sockaddr_storage *mcast_addr, + uint32_t seq_num, int server_tstamp, const char *client_id, size_t client_id_len, + const char *session_id, size_t session_id_len) +{ + size_t pos; + uint16_t u16; + + pos = 0; + + msg[pos++] = (unsigned char)MSG_TYPE_QUERY; + + if (tlv_add_version(msg, msg_len, &pos) == -1) + goto small_buf_err; + + if (client_id) { + if (tlv_add(msg, msg_len, &pos, TLV_OPT_TYPE_CLIENT_ID, client_id_len, + client_id) == -1) { + goto small_buf_err; + } + } + + if (tlv_add_seq_num(msg, msg_len, &pos, seq_num) == -1) + goto small_buf_err; + + if (tlv_add_client_tstamp(msg, msg_len, &pos) == -1) + goto small_buf_err; + + if (tlv_add_mcast_grp(msg, msg_len, &pos, mcast_addr) == -1) + goto small_buf_err; + + if (server_tstamp) { + u16 = TLV_OPT_TYPE_SERVER_TSTAMP; + + if (tlv_add_opt_request(msg, msg_len, &pos, &u16, 1) == -1) + goto small_buf_err; + } + + if (tlv_add(msg, msg_len, &pos, TLV_OPT_TYPE_SES_ID, session_id_len, session_id) == -1) + goto small_buf_err; + + return (pos); + +small_buf_err: + return (0); +} + +/* + * Create response message. msg is pointer to buffer where to store result message. msg_len is size + * of buffer. msg_decoded is decoded init message used for some informations (like client id, ...). + * mcast_grp and mcast_prefix are booleans used for decision, if to add Multicast Group and/or + * Multicast Prefix options. mcast_addr is value for mcast_grp and/or mcast_prefix. If none of this + * options is/are required, mcasr_addr can be set to NULL. ulticast address. session_id is + * session ID of client. + * + * Returned value is size of new message or 0 on fail (mostly because msg_len + * is smaller then needed). If success, new message is always at least 1 bytes long. + */ +size_t +msg_response_create(char *msg, size_t msg_len, const struct msg_decoded *msg_decoded, + int mcast_grp, int mcast_prefix, const struct sockaddr_storage *mcast_addr, + const char *session_id, size_t session_id_len) +{ + size_t pos; + + pos = 0; + + msg[pos++] = (unsigned char)MSG_TYPE_RESPONSE; + if (tlv_add_version(msg, msg_len, &pos) == -1) + goto small_buf_err; + + if (msg_decoded->client_id) { + if (tlv_add(msg, msg_len, &pos, TLV_OPT_TYPE_CLIENT_ID, msg_decoded->client_id_len, + msg_decoded->client_id) == -1) + goto small_buf_err; + } + + if (msg_decoded->seq_num_isset) { + if (tlv_add_seq_num(msg, msg_len, &pos, msg_decoded->seq_num) == -1) + goto small_buf_err; + } + + if (mcast_grp) { + if (tlv_add_mcast_grp(msg, msg_len, &pos, mcast_addr) == -1) + goto small_buf_err; + } + + if (msg_decoded->request_opt_server_info) { + if (tlv_add_server_info(msg, msg_len, &pos, PROGRAM_SERVER_INFO) == -1) + goto small_buf_err; + } + + if (mcast_prefix) { + if (tlv_add_mcast_prefix(msg, msg_len, &pos, mcast_addr) == -1) + goto small_buf_err; + } + + if (session_id) { + if (tlv_add(msg, msg_len, &pos, TLV_OPT_TYPE_SES_ID, session_id_len, + session_id) == -1) { + goto small_buf_err; + } + } + + return (pos); + +small_buf_err: + return (0); +} + +/* + * Update Server Timestamp option in message to current time stamp. msg is pointer to buffer with + * message and msg_len is length of message (without unused space). + * Function returns 0 on success, otherwise -1. + */ +int +msg_update_server_tstamp(char *msg, size_t msg_len) +{ + struct tlv_iterator tlv_iter; + size_t pos; + + memset(&tlv_iter, 0, sizeof(tlv_iter)); + tlv_iter_init(msg, msg_len, &tlv_iter); + + while (tlv_iter_next(&tlv_iter) != -1) { + if (tlv_iter_get_type(&tlv_iter) == TLV_OPT_TYPE_SERVER_TSTAMP) { + pos = tlv_iter.pos; + + if (tlv_add_server_tstamp(msg, msg_len, &pos) == -1) + goto add_tstamp_err; + } + } + + return (0); + +add_tstamp_err: + return (-1); +} diff --git a/msg.h b/msg.h new file mode 100644 index 0000000..d1dab31 --- /dev/null +++ b/msg.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#ifndef _MSG_H_ +#define _MSG_H_ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum { MSG_DECODED_OPT_REQUEST_LEN = 16 }; + +enum msg_type { + MSG_TYPE_INIT = 'I', + MSG_TYPE_RESPONSE = 'S', + MSG_TYPE_QUERY = 'Q', + MSG_TYPE_ANSWER = 'A', +}; + +struct msg_decoded { + struct timeval client_tstamp; + struct timeval server_tstamp; + enum msg_type msg_type; + size_t client_id_len; + size_t mcast_grp_len; + size_t opt_request_len; + size_t server_info_len; + size_t ses_id_len; + uint32_t seq_num; + int client_tstamp_isset; + int mcast_prefix_isset; + int request_opt_server_info; + int request_opt_server_tstamp; + int seq_num_isset; + int server_tstamp_isset; + const char *client_id; + const char *mcast_grp; + const char *server_info; + const char *ses_id; + uint8_t ttl; + uint8_t version; +}; + +extern size_t msg_answer_create(const char *orig_msg, size_t orig_msg_len, char *new_msg, + size_t new_msg_len, uint8_t ttl, int server_tstamp); + +extern void msg_decode(const char *msg, size_t msg_len, struct msg_decoded *decoded); + +extern size_t msg_init_create(char *msg, size_t msg_len, int req_si, + const struct sockaddr_storage *mcast_addr, const char *client_id, size_t client_id_len); + +extern size_t msg_query_create(char *msg, size_t msg_len, + const struct sockaddr_storage *mcast_addr, uint32_t seq_num, int server_tstamp, + const char *client_id, size_t client_id_len, const char *session_id, size_t session_id_len); + +extern size_t msg_response_create(char *msg, size_t msg_len, + const struct msg_decoded *msg_decoded, int mcast_grp, int mcast_prefix, + const struct sockaddr_storage *mcast_addr, const char *session_id, size_t session_id_len); + +extern int msg_update_server_tstamp(char *msg, size_t msg_len); + +#ifdef __cplusplus +} +#endif + +#endif /* _MSG_H_ */ diff --git a/msgsend.c b/msgsend.c new file mode 100644 index 0000000..4cf6989 --- /dev/null +++ b/msgsend.c @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#include + +#include + +#include +#include +#include + +#include + +#include "addrfunc.h" +#include "logging.h" +#include "msg.h" +#include "msgsend.h" +#include "omping.h" +#include "rsfunc.h" +#include "util.h" + +/* + * Send answer message. ucast_socket is socket used to send message, mcast_addr is used multicast + * address, orig_msg is received query message with orig_msg_len, decoded is decoded message, + * to is sockaddr_storage address of destination, ttl is set TTL and answer_type can specify what + * type of response to send. + * Function returns 0 on sucess, otherwise same error as rs_sendto or -4 if message cannot be + * created (usually due to small message buffer) + */ +int +ms_answer(int ucast_socket, const struct sockaddr_storage *mcast_addr, const char *orig_msg, + size_t orig_msg_len, const struct msg_decoded *decoded, const struct sockaddr_storage *to, + uint8_t ttl, enum ms_answer_type answer_type) +{ + char addr_str[INET6_ADDRSTRLEN]; + char new_msg[MAX_MSG_SIZE]; + struct sockaddr_storage to_mcast; + size_t new_msg_len; + ssize_t sent; + + new_msg_len = msg_answer_create(orig_msg, orig_msg_len, new_msg, sizeof(new_msg), + ttl, decoded->request_opt_server_tstamp); + + if (new_msg_len == 0) { + return (-4); + } + + if (answer_type == MS_ANSWER_UCAST || answer_type == MS_ANSWER_BOTH) { + af_sa_to_str(AF_CAST_SA(to), addr_str); + DEBUG_PRINTF("Sending unicast answer msg to %s", addr_str); + + msg_update_server_tstamp(new_msg, new_msg_len); + + sent = rs_sendto(ucast_socket, new_msg, new_msg_len, to); + + if (sent < 0) { + return (sent); + } + } + + if (answer_type == MS_ANSWER_MCAST || answer_type == MS_ANSWER_BOTH) { + af_copy_addr(mcast_addr, to, 1, 2, &to_mcast); + + af_sa_to_str(AF_CAST_SA(&to_mcast), addr_str); + DEBUG_PRINTF("Sending multicast answer msg to %s", addr_str); + + msg_update_server_tstamp(new_msg, new_msg_len); + + sent = rs_sendto(ucast_socket, new_msg, new_msg_len, &to_mcast); + + if (sent < 0) { + return (sent); + } + } + + return (0); +} + +/* + * Send init message. ucast_socket is socket used to send message, remote_addr is address of host + * to send message, mcast_addr is used multicast address, client_id is client id string with + * CLIENTID_LEN length, req_si should be non 0 if server information request is required. + * Function returns 0 on success, otherwise same error as rs_sendto or -4 if message cannot be + * created (usually due to small message buffer) + */ +int +ms_init(int ucast_socket, const struct sockaddr_storage *remote_addr, + const struct sockaddr_storage *mcast_addr, const char *client_id, int req_si) +{ + char addr_str[INET6_ADDRSTRLEN]; + char msg[MAX_MSG_SIZE]; + size_t msg_len; + ssize_t sent; + + af_sa_to_str(AF_CAST_SA(remote_addr), addr_str); + DEBUG_PRINTF("Sending init msg to %s", addr_str); + + msg_len = msg_init_create(msg, sizeof(msg), req_si, mcast_addr, client_id, CLIENTID_LEN); + + if (msg_len == 0) { + return (-4); + } + + sent = rs_sendto(ucast_socket, msg, msg_len, remote_addr); + + return (sent); +} + +/* + * Send query message. ucast_socket is socket used to send message, remote_addr is address of host + * to send message, mcast_addr is used multicast address, client_id is client id string with + * CLIENTID_LEN length, ses_id is Session ID string with ses_id_len length. seq_num is sequential + * number to set in packet. + * Function returns 0 on success, otherwise same error as rs_sendto or -4 if message cannot be + * created (usually due to small message buffer) + */ +int +ms_query(int ucast_socket, const struct sockaddr_storage *remote_addr, + const struct sockaddr_storage *mcast_addr, uint32_t seq_num, const char *client_id, + const char *ses_id, size_t ses_id_len) +{ + char addr_str[INET6_ADDRSTRLEN]; + char msg[MAX_MSG_SIZE]; + size_t msg_len; + ssize_t sent; + + af_sa_to_str(AF_CAST_SA(remote_addr), addr_str); + DEBUG_PRINTF("Sending query msg to %s", addr_str); + + msg_len = msg_query_create(msg, sizeof(msg), mcast_addr, seq_num, 0, client_id, + CLIENTID_LEN, ses_id, SESSIONID_LEN); + + if (msg_len == 0) { + return (-4); + } + + sent = rs_sendto(ucast_socket, msg, msg_len, remote_addr); + + return (sent); +} + +/* + * Send response message. ucast_socket is socket used to send message, mcast_addr is used multicast + * address, decoded is decoded message, to is sockaddr_storage address of destination, mcast_grp is + * used to distinguish if add or not add mcast group tlv, similarly to mcast_prefix. session_id and + * is session id string with session_id_len length. + * Function returns 0 on sucess, otherwise same error as rs_sendto or -4 if message cannot be + * created (usually due to small message buffer) + */ +int +ms_response(int ucast_socket, const struct sockaddr_storage *mcast_addr, + const struct msg_decoded *decoded, const struct sockaddr_storage *to, int mcast_grp, + int mcast_prefix, const char *session_id, size_t session_id_len) +{ + char addr_str[INET6_ADDRSTRLEN]; + char msg[MAX_MSG_SIZE]; + size_t msg_len; + ssize_t sent; + + af_sa_to_str((struct sockaddr *)to, addr_str); + DEBUG_PRINTF("Sending response msg to %s", addr_str); + + msg_len = msg_response_create(msg, sizeof(msg), decoded, mcast_grp, mcast_prefix, + mcast_addr, session_id, session_id_len); + + if (msg_len == 0) { + return (-4); + } + + sent = rs_sendto(ucast_socket, msg, msg_len, to); + + return (sent); +} + +/* + * Send response message with stop meaning. It's just shortcut to ms_send_response where + * parameters with same name has same meaning. Also returned values are same. + */ +int +ms_stop(int ucast_socket, const struct sockaddr_storage *mcast_addr, + const struct msg_decoded *decoded, const struct sockaddr_storage *to) +{ + return (ms_response(ucast_socket, mcast_addr, decoded, to, 0,0, NULL, 0)); +} diff --git a/msgsend.h b/msgsend.h new file mode 100644 index 0000000..3d7e1ba --- /dev/null +++ b/msgsend.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#ifndef _MSGSEND_H_ +#define _MSGSEND_H_ + +#include + +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum ms_answer_type { + MS_ANSWER_UCAST = 1, + MS_ANSWER_MCAST = 2, + MS_ANSWER_BOTH = 3, +}; + +extern int ms_answer(int ucast_socket, const struct sockaddr_storage *mcast_addr, + const char *orig_msg, size_t orig_msg_len, const struct msg_decoded *decoded, + const struct sockaddr_storage *to, uint8_t ttl, enum ms_answer_type answer_type); + +extern int ms_init(int ucast_socket, const struct sockaddr_storage *remote_addr, + const struct sockaddr_storage *mcast_addr, const char *client_id, int req_si); + +extern int ms_query(int ucast_socket, const struct sockaddr_storage *remote_addr, + const struct sockaddr_storage *mcast_addr, uint32_t seq_num, const char *client_id, + const char *ses_id, size_t ses_id_len); + +extern int ms_response(int ucast_socket, const struct sockaddr_storage *mcast_addr, + const struct msg_decoded *decoded, const struct sockaddr_storage *to, int mcast_grp, + int mcast_prefix, const char *session_id, size_t session_id_len); + +extern int ms_stop(int ucast_socket, const struct sockaddr_storage *mcast_addr, + const struct msg_decoded *decoded, const struct sockaddr_storage *to); + +#ifdef __cplusplus +} +#endif + +#endif /* _MSGSEND_H_ */ diff --git a/omping.8 b/omping.8 new file mode 100644 index 0000000..73eda67 --- /dev/null +++ b/omping.8 @@ -0,0 +1,273 @@ +.\" Copyright (c) 2010-2011, Red Hat, Inc. +.\" +.\" Permission to use, copy, modify, and/or distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE +.\" FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +.\" OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +.\" CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.\" Author: Jan Friesse +.\" +.Dd Jun 22, 2011 +.Dt OMPING 8 +.Os +. +.Sh NAME +.Nm omping +.Nd test IP multicast +.Sh SYNOPSIS +.Nm +.Op Fl 46CDEFqVv +.Op Fl c Ar count +.Op Fl i Ar interval +.Op Fl M Ar transport_method +.Op Fl m Ar mcast_addr +.Op Fl O Ar op_mode +.Op Fl p Ar port +.Op Fl R Ar rcvbuf +.Op Fl r Ar rate_limit +.Op Fl S Ar sndbuf +.Op Fl T Ar timeout +.Op Fl t Ar ttl +.Op Fl w Ar wait_time +.Ar remote_addr... +.Sh DESCRIPTION +The +.Nm +is program which uses User Datagram Protocol to determine if computer is able to send +and/or receive IP unicast and multicast or Broadcast packets from the network. It's designed to be +used in very similar way as +.Xr ping 8 +and also has some features of the +.Xr fping 8 +command. +Where +.Xr ping 8 +and +.Nm +differ is in who replies. In +.Xr ping 8 +replies are sent by the operating system and with +.Nm +another instance of +.Nm +sends the reply. This mean that +.Nm +must be running on all computers to test sending/receiving IP multicast/broadcast. +Its arguments are as follows: +.Bl -tag -width Ds +.It Fl 4 +Force usage of IPv4. +.It Fl 6 +Force usage of IPv6. +.It Fl C +Display continuous statistics for every reply message. +.It Fl D +Disable packet duplicate detection. Option is default for interval 0. +.It Fl E +Default behaviour when every client is in stop state is to exit. This may happen if all server sends +stop message or if +.Ar count +query messages was sent. This option changes default behaviour and +.Nm +doesn't quit automatically. +.It Fl F +Allow entering of arguments which are not allowed or not recommended by the specification. This is +typically the interval parameter. This option may be used multiple times. +.It Fl q +Quiet output. Nothing is displayed except state changes and summary. Option can be used twice and +then only summary is displayed. +.It Fl V +Display version and quit. Option can be used twice and then remote version is displayed. +.It Fl v +Set level of verbosity. Parameter can be used multiple times to achieve higher verbosity. +.It Fl c Ar count +Number of request packets to send to each target. After sending +.Ar count +query messages, given client is put to stop state and it is no longer sending query +messages. +.It Fl i Ar interval +Wait +.Ar interval +seconds between sending each request packet. Float values are supported in millisecond precision. +It's possible to set there 0 with meaning that packets are sent ether after previous unicast reply +is received or after 1 millisecond, depending on which of these intervals is smaller. The default +is to wait for one second between each packet. +.It Fl M Ar transport_method +Set transport method to use. This can be +.Cm asm +for Any-source Multicast, +.Cm ssm +for Source-specific Multicast and +.Cm ipbc +for IP Broadcast. +.It Fl m Ar mcast_addr +Multicast or broadcast address to listen on for multicast/broadcast answer messages. +Default is 232.43.211.234 for IPv4 and ff3e::4321:1234 for IPv6 multicast, or broadcast address of +local interface for Broadcast. +.It Fl O Ar op_mode +.Nm +can be running in three different modes. Default and recommended mode for quick testing is +.Cm normal +mode, when +.Nm +behaves like client and server together. It sends queries and is able to respond them. +.\" +.\" Temporarily disabled +.\" +.\" In +.\" .Cm server +.\" mode +.\" .Nm +.\" never sends it's own queries but responds to other nodes one. +Finally the +.Cm client +mode sends queries, but never respond to other nodes. +.It Fl p Ar port +Port to bind and listen on for both unicast and multicast/broadcast messages. Default is 4321. +.It Fl R Ar rcvbuf +Set socket rcvbuf. Minimum value for this option is 2048. If not specified, rcvbuf is not changed +and default OS provided value is used. +.It Fl r Ar rate_limit +Rate limit interval between two query messages to +.Ar rate_limit +seconds. Default value is same as +.Ar interval +given by +.Fl i +option. Rate limiting can be disabled by specifying 0 as value. Rate limiting is by default disabled +for +.Fl i +with 0 seconds. +.It Fl S Ar sndbuf +Set socket sndbuf. Minimum value for this option is 2048. If not specified, sndbuf is not changed +and default OS provided value is used. +.It Fl T Ar timeout +Specify a timeout, in seconds, before +.Nm +exits regardless of how many packets have +been received. +.It Fl t Ar ttl +Time-To-Live of sent packets. +.It Fl w Ar wait_time +after +.Nm +is stopped (by sending SIGINT or timeout expire) it is moved to special state when no queries are +made and server answer all queries by server response (stop message). This makes possible to give +correct (unbiased) result of lost packets on other nodes. Default is 3 times interval or 1 second, +depending which one is larger. Also special value 0 can be used to not wait at all or -1 which +means wait forever (this can be still terminated by sending SIGINT). +.It Ar remote_addr +List of addresses to test. One of them must be address of local internet interface. This +local address is used for bind and listening on for unicast packets. It's also used to determine +which interface should be used for sending multicast/broadcast replies. +.El +.Pp +Program is normally terminated by SIGINT. After signal receive summary is displayed. You can also +display summary during running by sending SIGINFO or SIGUSR1 signal. +.Pp +When using +.Nm +for fault isolation, it should first be run against local internet +interface only, to verify that the local network interface is up and running, and firewall +is correctly configured. This mode is available by entering only local IP address. +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +The following commands and output is a typical way how to find-out and solve network problems +using omping. In this situation, we have 3 computers named node-01, node-02 and node-03 with IP +addresses 192.168.1.101 - 192.168.1.103. Let's run the following command on all of them. +.Pp +.Dl $ omping node-01 node-02 node-03 +.Pp +on all of nodes we should be able to seen similar output +.Pp +.Dl node-01 : waiting for response msg +.Dl node-03 : waiting for response msg +.Dl node-01 : joined (S,G) = (*, 232.43.211.234), pinging +.Dl node-03 : joined (S,G) = (*, 232.43.211.234), pinging +.Dl node-01 : unicast, seq=1, size=69 bytes, dist=0, time=0.192ms +.Dl node-01 : multicast, seq=1, size=69 bytes, dist=0, time=0.284ms +.Dl node-03 : unicast, seq=1, size=69 bytes, dist=0, time=0.279ms +.Dl node-03 : multicast, seq=1, size=69 bytes, dist=0, time=0.360ms +.Pp +The first two lines tell us, that node-02 (actual node) is waiting for a response +message from node-01 and node-03. The second two lines contain information, that +we were successfully able to send an init message and also received a response +message from remote nodes. Both of these messages are unicast, so we are able to +send and receive unicast messages on a given port. If all of nodes are up and +.Nm +is running on all of them, but we are not able to receive a response +message, it's time to check connectivity between nodes. First make sure that +you are able to +.Xr ping 8 +them. If so, make sure that your firewall allows port 4321 to receive udp packets. +.Pp +The next line tells us that we were able to receive a 69 byte unicast response message from +node-01, with a sequence number of 1. The distance between the computers is 0 so they are on +the same link net. Time between send and receive packet was 0.192 ms, that is also the +current average time and lastly there were no lost packets. +.Pp +The 6th line tells us the same information as the previous one, but the received message +is a multicast message. It means, that multicast is probably well configured. +.Pp +The 7th and 8th lines are same as previous two one but for node-03. +.Pp +If the node is able to receive unicast packets, but never multicast, it means that multicast +configuration is incorrect. It's recommended to turn off your firewall. If multicast packets start +to arrive, great. If not, the problem is hidden in the switches/routers between the nodes. Contact +your system administrator to allow multicast traffic on the switch or router. +.Pp +.Nm +is terminated by SIGINT signal (CTRL-c). Summary statistics are shown +.Pp +.Dl node-01 : unicast, xmt/rcv/%loss = 18/18/0%, min/avg/max/std-dev = 0.177/0.301/0.463/0.073 +.Dl node-01 : multicast, xmt/rcv/%loss = 18/18/0%, min/avg/max/std-dev = 0.193/0.335/0.547/0.090 +.Dl node-03 : unicast, xmt/rcv/%loss = 21/21/0%, min/avg/max/std-dev = 0.272/0.299/0.327/0.017 +.Dl node-03 : multicast, xmt/rcv/%loss = 21/20/4% (seq>=2 0%), min/avg/max/std-dev = 0.347/0.388/0.575/0.055 +.Pp +Last line has additional information (seq>=2 %0) which means, that after receiving first multicast +packet with seq number 2, no other multicast packet was lost. Because creating multicast tree is +time consuming, it's pretty normal to lost first few multicast packets. rcv field can also be +formatted like +.Pp +.Dl node-01 : unicast, xmt/rcv/%loss = 3/3+1/0%, min/avg/max/std-dev = 0.294/0.299/0.305/0.006 +.Pp +This means, that 1 duplicate packet was received. It's possible to find out duplicate packet by +looking to output and find line which has following format +.Pp +.Dl node-01 : unicast, seq=2 (dup), size=69 bytes, dist=0, time=0.469ms +.Sh SEE ALSO +.Xr fping 8 , +.Xr ping 8 +.Sh STANDARDS +.Nm +uses Internet-Draft draft-ietf-mboned-ssmping-08 as underlaying protocol and tries +to be as compliant as possible. +.Sh AUTHORS +The +.Nm +utility was written by +.An Jan Friesse Aq jfriesse@redhat.com . +.Sh BUGS +.Bl -dash +.It +Some OSes may not have support for receiving TTL from packet. +.Nm +then cannot provide distance information. +.It +Some OSes may not provide information about packet receive. Less precise actual time is then used. +.It +.Nm +highly depends on precise +.Xr poll 2 +and +.Xr gettimeofday 3 +functions. If OS doesn't provide at least milliseconds precision, results may be incorrect. +.El diff --git a/omping.c b/omping.c new file mode 100644 index 0000000..2de2d53 --- /dev/null +++ b/omping.c @@ -0,0 +1,1543 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#include + +#define __STDC_FORMAT_MACROS +#define __STDC_LIMIT_MACROS +#include +#include +#include +#include +#include +#include +#include +#include + +#include "addrfunc.h" +#include "cli.h" +#include "logging.h" +#include "msg.h" +#include "msgsend.h" +#include "omping.h" +#include "rhfunc.h" +#include "rsfunc.h" +#include "sockfunc.h" +#include "tlv.h" +#include "util.h" + +#define MAX_EXIT_REQUESTS 2 + +/* + * Structure with internal omping data + */ +struct omping_instance { + struct ai_item local_addr; + struct ai_item mcast_addr; + struct rh_list remote_hosts; + struct ai_list remote_addrs; + enum omping_op_mode op_mode; + enum sf_transport_method transport_method; + char *local_ifname; + uint64_t send_count_queries; + int auto_exit; + int cont_stat; + int dup_buf_items; + int hn_max_len; + int ip_ver; + int mcast_socket; + int quiet; + int rate_limit_time; + int rcvbuf_size; + int single_addr; + int sndbuf_size; + int timeout_time; + int ucast_socket; + int wait_for_finish_time; + int wait_time; + unsigned int rh_no_active; + uint16_t port; + uint8_t ttl; +}; + +/* + * User requested exit of application (usually with SIGINT) + */ +static int exit_requested; + +/* + * User requested to display overall statistics (SIGINT/SIGUSR1) + */ +static int display_stats_requested; + +/* + * Function prototypes + */ +static int get_packet_loss_percent(uint64_t packet_sent, uint64_t packet_received); + +static int omping_check_msg_common(const struct msg_decoded *msg_decoded); + +static void omping_client_move_to_stop(struct omping_instance *instance, + struct rh_item *ri, enum rh_client_stop_reason stop_reason); + +static void omping_instance_create(struct omping_instance *instance, int argc, + char *argv[]); + +static void omping_instance_free(struct omping_instance *instance); + +static int omping_poll_receive_loop(struct omping_instance *instance, int timeout_time); + +static int omping_poll_timeout(struct omping_instance *instance, struct timeval *old_tstamp, + int timeout_time); + +static int omping_process_msg(struct omping_instance *instance, const char *msg, + size_t msg_len, const struct sockaddr_storage *from, uint8_t ttl, enum sf_cast_type cast_type, + struct timeval rp_timestamp); + +static int omping_process_answer_msg(struct omping_instance *instance, const char *msg, + size_t msg_len, const struct msg_decoded *msg_decoded, const struct sockaddr_storage *from, + uint8_t ttl, enum sf_cast_type cast_type, struct timeval rp_timestamp); + +static int omping_process_init_msg(struct omping_instance *instance, const char *msg, + size_t msg_len, const struct msg_decoded *msg_decoded, const struct sockaddr_storage *from, + struct timeval rp_timestamp); + +static int omping_process_query_msg(struct omping_instance *instance, const char *msg, + size_t msg_len, const struct msg_decoded *msg_decoded, const struct sockaddr_storage *from, + struct timeval rp_timestamp); + +static int omping_process_response_msg(struct omping_instance *instance, const char *msg, + size_t msg_len, const struct msg_decoded *msg_decoded, const struct sockaddr_storage *from); + +static int omping_send_client_query(struct omping_instance *instance, struct rh_item *ri, + int increase); + +static int omping_send_client_msgs(struct omping_instance *instance); + +static void omping_send_receive_loop(struct omping_instance *instance, int timeout_time, + int final_stats, int allow_auto_exit); + +static void print_client_state(const char *host_name, int host_name_len, + enum sf_transport_method transport_method, const struct sockaddr_storage *mcast_addr, + const struct sockaddr_storage *remote_addr, enum rh_client_state state, + enum rh_client_stop_reason stop_reason); + +static void print_final_remote_version(const struct rh_list *remote_hosts, int host_name_len); + +static void print_final_stats(const struct rh_list *remote_hosts, int host_name_len, + enum sf_transport_method transport_method); + +static void print_packet_stats(const char *host_name, int host_name_len, uint32_t seq, + int is_dup, size_t msg_len, int dist_set, uint8_t dist, int rtt_set, double rtt, + double avg_rtt, int loss, enum sf_cast_type cast_type, int cont_stat); + +static void siginfo_handler(int sig); +static void sigint_handler(int sig); + +static void register_signal_handlers(void); + +/* + * Functions implementation + */ + +/* + * Entry point of omping + */ +int +main(int argc, char *argv[]) +{ + struct omping_instance instance; + int allow_auto_exit; + int final_stats; + int wait_for_finish_time; + + omping_instance_create(&instance, argc, argv); + + register_signal_handlers(); + + if (instance.op_mode == OMPING_OP_MODE_SERVER) { + final_stats = allow_auto_exit = 0; + } else { + final_stats = allow_auto_exit = 1; + } + + omping_send_receive_loop(&instance, instance.timeout_time, final_stats, allow_auto_exit); + + if (!instance.single_addr && instance.wait_for_finish_time != 0 && + instance.op_mode != OMPING_OP_MODE_CLIENT) { + exit_requested = 0; + + DEBUG_PRINTF("Moving all clients to stop state and server to finishing state"); + rh_list_put_to_finish_state(&instance.remote_hosts, RH_LFS_BOTH); + + if (instance.wait_for_finish_time == -1) { + wait_for_finish_time = 0; + } else { + wait_for_finish_time = instance.wait_for_finish_time; + } + + VERBOSE_PRINTF("Waiting for %d ms to inform other nodes about instance exit", + instance.wait_for_finish_time); + + omping_send_receive_loop(&instance, wait_for_finish_time, 0, 0); + } + + omping_instance_free(&instance); + + return 0; +} + +/* + * Compute packet loss in percent from number of send and received packets + */ +static int +get_packet_loss_percent(uint64_t packet_sent, uint64_t packet_received) +{ + int loss; + + if (packet_received > packet_sent) { + DEBUG_PRINTF("packet_received > packet_sent"); + loss = 0; + } else { + loss = (int)((1.0 - (double)packet_received / (double)packet_sent) * 100.0); + } + + return (loss); +} + +/* + * Test basic message characteristics. Return 0 on success, and -1 on fail. + */ +static int +omping_check_msg_common(const struct msg_decoded *msg_decoded) +{ + if (msg_decoded->msg_type != MSG_TYPE_INIT && msg_decoded->msg_type != MSG_TYPE_RESPONSE && + msg_decoded->msg_type != MSG_TYPE_QUERY && msg_decoded->msg_type != MSG_TYPE_ANSWER) { + DEBUG_PRINTF("Unknown type %c (0x%X) of message", msg_decoded->msg_type, + msg_decoded->msg_type); + + return (-1); + } + + if (msg_decoded->version != 2) { + DEBUG_PRINTF("Message version %d is not supported", msg_decoded->version); + + return (-1); + } + + return (0); +} + +/* + * Move client to stop state. Instance is omping instance, ri is pointer to remote host item from + * remote hosts list and stop_reason is reason to stop. + */ +static void +omping_client_move_to_stop(struct omping_instance *instance, struct rh_item *ri, + enum rh_client_stop_reason stop_reason) +{ + ri->client_info.state = RH_CS_STOP; + instance->rh_no_active--; + + if (instance->quiet < 2) { + print_client_state(ri->addr->host_name, instance->hn_max_len, + instance->transport_method, NULL, &ri->addr->sas, + RH_CS_STOP, stop_reason); + } +} + +/* + * Create instance of omping. argc and argv are taken form main function. Result is stored in + * instance parameter + */ +static void +omping_instance_create(struct omping_instance *instance, int argc, char *argv[]) +{ + uint16_t bind_port; + + bind_port = 0; + memset(instance, 0, sizeof(struct omping_instance)); + + cli_parse(&instance->remote_addrs, argc, argv, &instance->local_ifname, &instance->ip_ver, + &instance->local_addr, &instance->wait_time, &instance->transport_method, + &instance->mcast_addr, &instance->port, &instance->ttl, &instance->single_addr, + &instance->quiet, &instance->cont_stat, &instance->timeout_time, + &instance->wait_for_finish_time, &instance->dup_buf_items, &instance->rate_limit_time, + &instance->sndbuf_size, &instance->rcvbuf_size, &instance->send_count_queries, + &instance->auto_exit, &instance->op_mode); + + rh_list_create(&instance->remote_hosts, &instance->remote_addrs, instance->dup_buf_items, + instance->rate_limit_time); + + instance->rh_no_active = rh_list_length(&instance->remote_hosts); + + instance->ucast_socket = + sf_create_unicast_socket(AF_CAST_SA(&instance->local_addr.sas), instance->ttl, 1, + instance->single_addr, instance->local_ifname, instance->transport_method, 1, 0, + instance->sndbuf_size, instance->rcvbuf_size, + (instance->op_mode == OMPING_OP_MODE_CLIENT ? &bind_port : NULL)); + + if (instance->ucast_socket == -1) { + err(1, "Can't create/bind unicast socket"); + } + + switch (instance->op_mode) { + case OMPING_OP_MODE_SERVER: + instance->mcast_socket = -1; + rh_list_put_to_finish_state(&instance->remote_hosts, RH_LFS_CLIENT); + break; + case OMPING_OP_MODE_SHOW_VERSION: + rh_list_put_to_finish_state(&instance->remote_hosts, RH_LFS_SERVER); + break; + case OMPING_OP_MODE_CLIENT: + rh_list_put_to_finish_state(&instance->remote_hosts, RH_LFS_SERVER); + case OMPING_OP_MODE_NORMAL: + instance->mcast_socket = + sf_create_multicast_socket((struct sockaddr *)&instance->mcast_addr.sas, + AF_CAST_SA(&instance->local_addr.sas), instance->local_ifname, + instance->ttl, instance->single_addr, instance->transport_method, + &instance->remote_addrs, 1, 0, instance->sndbuf_size, + instance->rcvbuf_size, + (instance->op_mode == OMPING_OP_MODE_CLIENT ? bind_port : 0)); + + if (instance->mcast_socket == -1) { + err(1, "Can't create/bind multicast socket"); + } + break; + } + + util_random_init(&instance->local_addr.sas); + + rh_list_gen_cid(&instance->remote_hosts, &instance->local_addr); + + instance->hn_max_len = rh_list_hn_max_len(&instance->remote_hosts); +} + +/* + * Free allocated memory of omping instance. + */ +static void +omping_instance_free(struct omping_instance *instance) +{ + af_ai_list_free(&instance->remote_addrs); + rh_list_free(&instance->remote_hosts); + + free(instance->local_addr.host_name); + free(instance->mcast_addr.host_name); + free(instance->local_ifname); +} + +/* + * Loop for receiving messages for given time (instance->wait_time) and process them. Instance is + * omping instance. timeout_time is maximum time to wait. + * Function returns 0 on success, or -2 on EINTR. + */ +static int +omping_poll_receive_loop(struct omping_instance *instance, int timeout_time) +{ + char msg[MAX_MSG_SIZE]; + struct sockaddr_storage from; + struct timeval old_tstamp; + struct timeval rp_timestamp; + enum sf_cast_type cast_type; + int i; + int poll_res; + int receive_res; + uint8_t ttl; + int res; + + memset(&old_tstamp, 0, sizeof(old_tstamp)); + + do { + poll_res = omping_poll_timeout(instance, &old_tstamp, timeout_time); + if (poll_res == -2) { + return (-2); + /* NOTREACHED */ + } + + for (i = 0; i < 2; i++) { + receive_res = 0; + + if (i == 0 && poll_res & 1) { + receive_res = rs_receive_msg(instance->ucast_socket, &from, msg, + sizeof(msg), &ttl, &rp_timestamp); + } + + if (i == 1 && poll_res & 2) { + receive_res = rs_receive_msg(instance->mcast_socket, &from, msg, + sizeof(msg), &ttl, &rp_timestamp); + } + + switch (receive_res) { + case -1: + err(2, "Cannot receive message"); + /* NOTREACHED */ + break; + case -2: + return (-2); + /* NOTREACHED */ + break; + case -3: + warn("Cannot receive message"); + break; + case -4: + VERBOSE_PRINTF("Received message too long"); + break; + } + + if (receive_res > 0) { + if (i == 0) { + cast_type = SF_CT_UNI; + } else { + switch (instance->transport_method) { + case SF_TM_ASM: + case SF_TM_SSM: + cast_type = SF_CT_MULTI; + break; + case SF_TM_IPBC: + cast_type = SF_CT_BROAD; + break; + default: + DEBUG_PRINTF("Internal error - unknown tm"); + errx(1, "Internal error - unknown tm"); + /* NOTREACHED */ + } + } + + res = omping_process_msg(instance, msg, receive_res, &from, ttl, + cast_type, rp_timestamp); + + if (res == -2) { + return (-2); + } + } + } + } while (poll_res > 0); + + return (0); +} + +/* + * Wait for messages on sockets. instance is omping_instance and old_tstamp is temporary variable + * which must be set to zero on first call. Function handles EINTR for display statistics. + * Function is wrapper on top of rs_poll_timeout, but handles -1 error code. Other return values + * have same meaning. timeout_time is maximum time to wait + */ +static int +omping_poll_timeout(struct omping_instance *instance, struct timeval *old_tstamp, int timeout_time) +{ + int poll_res; + + do { + poll_res = rs_poll_timeout(instance->ucast_socket, instance->mcast_socket, + timeout_time, old_tstamp); + + switch (poll_res) { + case -1: + err(2, "Cannot poll on sockets"); + /* NOTREACHED */ + break; + case -2: + if (display_stats_requested) { + display_stats_requested = 0; + + if (instance->op_mode == OMPING_OP_MODE_SHOW_VERSION) { + print_final_remote_version(&instance->remote_hosts, + instance->hn_max_len); + } else { + print_final_stats(&instance->remote_hosts, + instance->hn_max_len, instance->transport_method); + } + + printf("\n"); + + if (!exit_requested) { + break; + } + } + + return (-2); + /* NOTREACHED */ + break; + } + } while (poll_res < 0); + + return (poll_res); +} + +/* + * Process received message. Instance is omping instance, msg is received message with msg_len + * length, from is source of message. ttl is packet Time-To-Live or 0, if that information was not + * available. cast_type is type of packet received (unicast/multicast/broadcast). rp_timestamp + * is receiving time of packet. + * Function returns 0 on success or -2 on EINTR. + */ +static int +omping_process_msg(struct omping_instance *instance, const char *msg, size_t msg_len, + const struct sockaddr_storage *from, uint8_t ttl, enum sf_cast_type cast_type, + struct timeval rp_timestamp) +{ + char addr_str[INET6_ADDRSTRLEN]; + struct msg_decoded msg_decoded; + const char *cast_str; + struct rh_item *rh_item; + int res; + + res = 0; + + msg_decode(msg, msg_len, &msg_decoded); + + cast_str = sf_cast_type_to_str(cast_type); + + af_sa_to_str((struct sockaddr *)from, addr_str); + DEBUG_PRINTF("Received %scast message from %s type %c (0x%X), len %zu", cast_str, addr_str, + msg_decoded.msg_type, msg_decoded.msg_type, msg_len); + + if (omping_check_msg_common(&msg_decoded) == -1) { + res = ms_stop(instance->ucast_socket, &instance->mcast_addr.sas, &msg_decoded, + from); + } else { + switch (msg_decoded.msg_type) { + case MSG_TYPE_INIT: + if (cast_type != SF_CT_UNI) + goto error_unknown_mcast; + + if (instance->op_mode == OMPING_OP_MODE_CLIENT) + goto error_unknown_msg_type; + + res = omping_process_init_msg(instance, msg, msg_len, &msg_decoded, from, + rp_timestamp); + break; + case MSG_TYPE_RESPONSE: + if (cast_type != SF_CT_UNI) + goto error_unknown_mcast; + + if (instance->op_mode == OMPING_OP_MODE_SERVER) + goto error_unknown_msg_type; + + res = omping_process_response_msg(instance, msg, msg_len, &msg_decoded, + from); + break; + case MSG_TYPE_QUERY: + if (cast_type != SF_CT_UNI) + goto error_unknown_mcast; + + if (instance->op_mode == OMPING_OP_MODE_CLIENT) + goto error_unknown_msg_type; + + res = omping_process_query_msg(instance, msg, msg_len, &msg_decoded, from, + rp_timestamp); + break; + case MSG_TYPE_ANSWER: + if (instance->op_mode == OMPING_OP_MODE_SERVER && cast_type == SF_CT_UNI) + goto error_unknown_msg_type; + + res = omping_process_answer_msg(instance, msg, msg_len, &msg_decoded, from, + ttl, cast_type, rp_timestamp); + break; + } + } + + switch (res) { + case -1: + err(2, "Cannot send message"); + /* NOTREACHED */ + break; + case -2: + return (-2); + /* NOTREACHED */ + break; + case -3: + warn("Send message error"); + rh_item = rh_list_find(&instance->remote_hosts, (const struct sockaddr *)from); + if (rh_item == NULL) { + DEBUG_PRINTF("Received message from unknown address"); + } else { + rh_item->client_info.no_err_msgs++; + } + break; + case -4: + DEBUG_PRINTF("Cannot send message. Buffer too small"); + break; + } + + return (0); + +error_unknown_mcast: + DEBUG_PRINTF("Received multicast message with invalid type %c (0x%X)", + msg_decoded.msg_type, msg_decoded.msg_type); + + return (0); + +error_unknown_msg_type: + DEBUG_PRINTF("Received message type %c (0x%X) which is not supported in given " + "operational mode", msg_decoded.msg_type, msg_decoded.msg_type); + + return (0); +} + +/* + * Function to test if packet is duplicate. ci is client item information, seq is sequential number + * and cast_type is type of packet received (unicast/multicast/broadcast). + * Function returns 0 if packet is not duplicate, otherwise 1. + */ +static int +is_dup_packet(const struct rh_item_ci *ci, uint32_t seq, enum sf_cast_type cast_type) +{ + int cast_index; + int res; + + cast_index = (cast_type == SF_CT_UNI ? 0 : 1); + + if (ci->dup_buffer[cast_index][seq % ci->dup_buf_items] == seq) { + res = 1; + } else { + ci->dup_buffer[cast_index][seq % ci->dup_buf_items] = seq; + + res = 0; + } + + return (res); +} + +/* + * Process answer message. Instance is omping instance, msg is received message with msg_len length, + * msg_decoded is decoded message, from is address of sender. ttl is Time-To-Live of packet. If ttl + * is 0, it means that it was not possible to find out ttl. cast_type is type of packet received + * (unicast/multicast/broadcast). rp_timestamp is receiving time of packet. + * Function returns 0 on sucess, otherwise same error as rs_sendto or -4 if message cannot be + * created (usually due to small message buffer), or -5 if message is invalid (not for us, message + * without client_id, ...). + */ +static int +omping_process_answer_msg(struct omping_instance *instance, const char *msg, size_t msg_len, + const struct msg_decoded *msg_decoded, const struct sockaddr_storage *from, uint8_t ttl, + enum sf_cast_type cast_type, struct timeval rp_timestamp) +{ + struct rh_item *rh_item; + double avg_rtt; + double rtt; + uint64_t received; + uint64_t sent; + int cast_index; + int dist_set; + int first_packet; + int is_dup; + int rtt_set; + int loss; + uint8_t dist; + + rh_item = rh_list_find(&instance->remote_hosts, (const struct sockaddr *)from); + if (rh_item == NULL) { + DEBUG_PRINTF("Received message from unknown address"); + return (-5); + } + + if (msg_decoded->client_id == NULL) { + DEBUG_PRINTF("Message doesn't contain client id"); + return (-5); + } + + if (msg_decoded->client_id_len != CLIENTID_LEN || + memcmp(msg_decoded->client_id, rh_item->client_info.client_id, CLIENTID_LEN) != 0) { + DEBUG_PRINTF("Message doesn't contain our client id"); + return (-5); + } + + if (!msg_decoded->seq_num_isset) { + DEBUG_PRINTF("Message doesn't contain seq num"); + return (-5); + } + + if (rh_item->client_info.state != RH_CS_QUERY) { + DEBUG_PRINTF("Client is not in query state. Ignoring message"); + return (-5); + } + + if (ttl > 0 && msg_decoded->ttl > 0) { + dist_set = 1; + dist = msg_decoded->ttl - ttl; + } else { + dist_set = dist = 0; + } + + if (msg_decoded->client_tstamp_isset) { + rtt_set = 1; + rtt = util_time_double_absdiff_ns(msg_decoded->client_tstamp, rp_timestamp); + } else { + rtt_set = 0; + rtt = 0; + } + + avg_rtt = 0; + cast_index = (cast_type == SF_CT_UNI ? 0 : 1); + is_dup = 0; + + if (instance->dup_buf_items > 0) { + is_dup = is_dup_packet(&rh_item->client_info, msg_decoded->seq_num, cast_type); + } + + if (is_dup) { + if (rh_item->client_info.no_dups[cast_index] == ((uint64_t)~0)) { + DEBUG_PRINTF("Number of received duplicates for %s exhausted.", + rh_item->addr->host_name); + } else { + rh_item->client_info.no_dups[cast_index]++; + } + + received = rh_item->client_info.no_received[cast_index]; + } else { + first_packet = (rh_item->client_info.no_received[cast_index] == 0); + + received = ++rh_item->client_info.no_received[cast_index]; + + if (cast_index == 0) { + rh_item->client_info.lru_seq_num = msg_decoded->seq_num; + } + + if (cast_type != SF_CT_UNI && first_packet && + !rh_item->client_info.seq_num_overflow) { + rh_item->client_info.first_mcast_seq = msg_decoded->seq_num; + } + + if (rtt_set) { + util_ov_update(&rh_item->client_info.avg_rtt[cast_index], + &rh_item->client_info.m2_rtt[cast_index], rtt, received); + + if (first_packet) { + rh_item->client_info.rtt_max[cast_index] = rtt; + rh_item->client_info.rtt_min[cast_index] = rtt; + } else { + if (rtt > rh_item->client_info.rtt_max[cast_index]) { + rh_item->client_info.rtt_max[cast_index] = rtt; + } + + if (rtt < rh_item->client_info.rtt_min[cast_index]) { + rh_item->client_info.rtt_min[cast_index] = rtt; + } + } + } + } + + if (instance->cont_stat) { + sent = rh_item->client_info.no_sent; + + if (cast_type != SF_CT_UNI && rh_item->client_info.first_mcast_seq > 0) { + sent = sent - rh_item->client_info.first_mcast_seq + 1; + } + loss = get_packet_loss_percent(sent, received); + avg_rtt = rh_item->client_info.avg_rtt[cast_index] / UTIL_NSINMS; + } else { + loss = 0; + } + + if (instance->quiet == 0) { + print_packet_stats(rh_item->addr->host_name, instance->hn_max_len, + msg_decoded->seq_num, is_dup, msg_len, dist_set, dist, rtt_set, + rtt / UTIL_NSINMS, avg_rtt, loss, cast_type, instance->cont_stat); + } + + return (0); +} + +/* + * Process init messge. instance is omping_instance, msg is received message with msg_len length, + * msg_decoded is decoded message and from is sockaddr of sender. rp_timestamp is receiving time + * of packet. + * Function returns 0 on sucess, otherwise same error as rs_sendto or -4 if message cannot be + * created (usually due to small message buffer) + */ +static int +omping_process_init_msg(struct omping_instance *instance, const char *msg, size_t msg_len, + const struct msg_decoded *msg_decoded, const struct sockaddr_storage *from, + struct timeval rp_timestamp) +{ + struct rh_item *rh_item; + struct tlv_iterator tlv_iter; + int pref_found; + + rh_item = rh_list_find(&instance->remote_hosts, (const struct sockaddr *)from); + if (rh_item == NULL) { + DEBUG_PRINTF("Received message from unknown address"); + + return (ms_stop(instance->ucast_socket, &instance->mcast_addr.sas, + msg_decoded, from)); + } + + if (rh_item->server_info.state == RH_SS_FINISHING) { + DEBUG_PRINTF("We are in finishing state. Sending request to stop."); + + return (ms_stop(instance->ucast_socket, &instance->mcast_addr.sas, + msg_decoded, from)); + } + + if (!msg_decoded->mcast_prefix_isset) { + DEBUG_PRINTF("Mcast prefix is not set"); + + return (ms_response(instance->ucast_socket, &instance->mcast_addr.sas, + msg_decoded, from, 0, 1, NULL, 0)); + } + + pref_found = 0; + + tlv_iter_init(msg, msg_len, &tlv_iter); + while (tlv_iter_next(&tlv_iter) == 0) { + if (tlv_iter_get_type(&tlv_iter) == TLV_OPT_TYPE_MCAST_PREFIX) { + if (tlv_iter_pref_eq(&tlv_iter, &instance->mcast_addr.sas)) { + pref_found = 1; + + break; + } + } + } + + if (!pref_found) { + DEBUG_PRINTF("Can't find required prefix"); + + return (ms_response(instance->ucast_socket, &instance->mcast_addr.sas, msg_decoded, + from, 0, 1, NULL, 0)); + } + + if (util_time_absdiff(rh_item->server_info.last_init_ts, rp_timestamp) < + DEFAULT_WAIT_TIME) { + DEBUG_PRINTF("Time diff between two init messages too short. Ignoring message."); + return (0); + } + + util_gen_sid(rh_item->server_info.ses_id); + rh_item->server_info.state = RH_SS_ANSWER; + rh_item->server_info.last_init_ts = rp_timestamp; + + return (ms_response(instance->ucast_socket, &instance->mcast_addr.sas, msg_decoded, from, + 1, 0, rh_item->server_info.ses_id, SESSIONID_LEN)); +} + +/* + * Process query msg. instance is omping instance, msg is received message with msg_len length, + * msg_decoded is decoded message and from is sender of message. rp_timestamp is receiving time + * of packet. + * Function returns 0 on sucess, otherwise same error as rs_sendto or -4 if message cannot be + * created (usually due to small message buffer) + */ +static int +omping_process_query_msg(struct omping_instance *instance, const char *msg, size_t msg_len, + const struct msg_decoded *msg_decoded, const struct sockaddr_storage *from, + struct timeval rp_timestamp) +{ + struct rh_item *rh_item; + + rh_item = rh_list_find(&instance->remote_hosts, (const struct sockaddr *)from); + if (rh_item == NULL) { + DEBUG_PRINTF("Received message from unknown address"); + + return (ms_stop(instance->ucast_socket, &instance->mcast_addr.sas, + msg_decoded, from)); + } + + if (rh_item->server_info.state != RH_SS_ANSWER) { + DEBUG_PRINTF("Server is not in answer state"); + + return (ms_stop(instance->ucast_socket, &instance->mcast_addr.sas, + msg_decoded, from)); + } + + if (!msg_decoded->seq_num_isset || msg_decoded->mcast_grp == NULL) { + DEBUG_PRINTF("Received message doesn't have mcast group set"); + + return (ms_stop(instance->ucast_socket, &instance->mcast_addr.sas, + msg_decoded, from)); + } + + if (msg_decoded->ses_id_len != SESSIONID_LEN || + memcmp(msg_decoded->ses_id, rh_item->server_info.ses_id, SESSIONID_LEN) != 0) { + DEBUG_PRINTF("Received message session id isn't expected"); + + return (ms_stop(instance->ucast_socket, &instance->mcast_addr.sas, + msg_decoded, from)); + } + + /* + * Rate limiting + */ + if (instance->rate_limit_time > 0) { + if (gcra_rl(&rh_item->server_info.gcra, rp_timestamp) == 0) { + DEBUG_PRINTF("Received message rate limited"); + return (0); + } + } + + /* + * Answer to query message + */ + return (ms_answer(instance->ucast_socket, &instance->mcast_addr.sas, msg, msg_len, + msg_decoded, from, instance->ttl, MS_ANSWER_BOTH)); +} + +/* + * Process response message. Instance is omping instance, msg is received message with msg_len + * length, msg_decoded is decoded message and from is address of sender. + * Function returns 0 on sucess, otherwise same error as rs_sendto or -4 if message cannot be + * created (usually due to small message buffer), or -5 if message is invalid (not for us, message + * without client_id, ...). + */ +static int +omping_process_response_msg(struct omping_instance *instance, const char *msg, size_t msg_len, + const struct msg_decoded *msg_decoded, const struct sockaddr_storage *from) +{ + struct rh_item *rh_item; + enum rh_client_state old_cstate; + const char *ci_ses_id; + const char *msg_ses_id; + int send_res; + + rh_item = rh_list_find(&instance->remote_hosts, (const struct sockaddr *)from); + if (rh_item == NULL) { + DEBUG_PRINTF("Received message from unknown address"); + + return (-5); + } + + if (rh_item->client_info.state == RH_CS_STOP) { + DEBUG_PRINTF("Client is in stop state. Ignoring message."); + + return (-5); + } + + if (msg_decoded->client_id == NULL) { + DEBUG_PRINTF("Message doesn't contain client id"); + + return (-5); + } + + if (msg_decoded->client_id_len != CLIENTID_LEN || + memcmp(msg_decoded->client_id, rh_item->client_info.client_id, CLIENTID_LEN) != 0) { + DEBUG_PRINTF("Message doesn't contain our client id"); + + return (-5); + } + + if (instance->op_mode == OMPING_OP_MODE_SHOW_VERSION) { + if (msg_decoded->server_info_len > 0) { + rh_item->client_info.server_info_len = msg_decoded->server_info_len; + + free(rh_item->client_info.server_info); + + rh_item->client_info.server_info = + (char *)malloc(rh_item->client_info.server_info_len); + + if (rh_item->client_info.server_info == NULL) { + errx(1, "Can't alloc memory"); + } + + memcpy(rh_item->client_info.server_info, msg_decoded->server_info, + rh_item->client_info.server_info_len); + + omping_client_move_to_stop(instance, rh_item, + RH_CSR_REMOTE_VERSION_RECEIVED); + } else { + DEBUG_PRINTF("Message doesn't contain server information"); + + return (-5); + } + + return (0); + } + + if (msg_decoded->mcast_grp == NULL || msg_decoded->mcast_grp_len == 0) { + DEBUG_PRINTF("Server doesn't send us multicast group"); + + if (rh_item->client_info.state == RH_CS_QUERY) { + DEBUG_PRINTF("Client was in query state. Put to initial state"); + + rh_item->client_info.state = RH_CS_INITIAL; + /* + * Technically, packet was sent and also received so no lost at all + */ + rh_item->client_info.no_sent--; + + util_gen_cid(rh_item->client_info.client_id, &instance->local_addr); + } else { + DEBUG_PRINTF("Client was not in query state. Put it to stop state"); + omping_client_move_to_stop(instance, rh_item, RH_CSR_SERVER); + } + + return (-5); + } + + if (!(tlv_mcast_grp_eq(&instance->mcast_addr.sas, msg_decoded->mcast_grp, + msg_decoded->mcast_grp_len))) { + DEBUG_PRINTF("Server send us different multicast group then expected"); + + } + + if (msg_decoded->ses_id == NULL) { + DEBUG_PRINTF("Message doesn't contain session id"); + + return (-5); + } + + if (rh_item->client_info.ses_id_len == msg_decoded->ses_id_len) { + ci_ses_id = rh_item->client_info.ses_id; + msg_ses_id = msg_decoded->ses_id; + + if (memcmp(ci_ses_id, msg_ses_id, msg_decoded->ses_id_len) == 0) { + DEBUG_PRINTF("Duplicate server response"); + + return (-5); + } + } + + old_cstate = rh_item->client_info.state; + rh_item->client_info.state = RH_CS_QUERY; + rh_item->client_info.ses_id_len = msg_decoded->ses_id_len; + + free(rh_item->client_info.ses_id); + + rh_item->client_info.ses_id = (char *)malloc(rh_item->client_info.ses_id_len); + if (rh_item->client_info.ses_id == NULL) { + errx(1, "Can't alloc memory"); + } + + memcpy(rh_item->client_info.ses_id, msg_decoded->ses_id, rh_item->client_info.ses_id_len); + + if (old_cstate == RH_CS_INITIAL) { + if (instance->quiet < 2) { + print_client_state(rh_item->addr->host_name, instance->hn_max_len, + instance->transport_method, &instance->mcast_addr.sas, + &rh_item->addr->sas, RH_CS_QUERY, RH_CSR_NONE); + } + } + + send_res = omping_send_client_query(instance, rh_item, (old_cstate == RH_CS_INITIAL)); + + return (send_res); +} + +/* + * Send client query message. instance is omping instance. ri is one item fro rh_list and it's + * client to process. increase is boolean variable. If set, seq_num and no_sent packets are + * increased. + * Function return 0 on success, otherwise same error as rs_sendto or -4 if message cannot be + * created (usually due to small message buffer) + */ +static int +omping_send_client_query(struct omping_instance *instance, struct rh_item *ri, int increase) +{ + struct rh_item_ci *ci; + int send_res; + + ci = &ri->client_info; + + if (increase) { + if (ci->no_sent + 1 == ((uint64_t)~0)) { + omping_client_move_to_stop(instance, ri, RH_CSR_SEND_MAXIMUM); + DEBUG_PRINTF("Maximum number of sent messages for %s exhausted. " + "Moving to stop state.", ri->addr->host_name); + + return (0); + } + + if (instance->send_count_queries > 0 && + ci->no_sent + 1 > instance->send_count_queries) { + omping_client_move_to_stop(instance, ri, RH_CSR_TO_SEND_EXHAUSTED); + DEBUG_PRINTF("Number of messages to be sent by %s exhausted. " + "Moving to stop state.", ri->addr->host_name); + + return (0); + } + + ci->seq_num++; + ci->no_sent++; + + if (ci->seq_num == 0) { + ci->seq_num_overflow = 1; + ci->seq_num++; + } + } + + send_res = ms_query(instance->ucast_socket, &ri->addr->sas, &instance->mcast_addr.sas, + ci->seq_num, ci->client_id, ci->ses_id, ci->ses_id_len); + + return (send_res); +} + +/* + * Send client init or request messages to all of remote hosts. instance is omping instance. + * Function return 0 on success, or -2 on EINTR. + */ +static int +omping_send_client_msgs(struct omping_instance *instance) +{ + struct rh_item *remote_host; + struct rh_item_ci *ci; + int send_res; + + TAILQ_FOREACH(remote_host, &instance->remote_hosts, entries) { + send_res = 0; + ci = &remote_host->client_info; + + switch (ci->state) { + case RH_CS_INITIAL: + /* + * Initial message is send at most after DEFAULT_WAIT_TIME + */ + if (util_time_absdiff(ci->last_init_ts, util_get_time()) > + DEFAULT_WAIT_TIME) { + if (instance->quiet < 2) { + print_client_state(remote_host->addr->host_name, + instance->hn_max_len, instance->transport_method, NULL, + &remote_host->addr->sas, RH_CS_INITIAL, RH_CSR_NONE); + } + + send_res = ms_init(instance->ucast_socket, &remote_host->addr->sas, + &instance->mcast_addr.sas, ci->client_id, + (instance->op_mode == OMPING_OP_MODE_SHOW_VERSION ? 1 : 0)); + + ci->last_init_ts = util_get_time(); + } + break; + case RH_CS_QUERY: + if (instance->wait_time == 0) { + /* + * Handle wait time zero specifically. Send query if answer for + * previous query received or after 1ms. + */ + if (ci->lru_seq_num == ci->seq_num || + util_time_absdiff(ci->last_query_ts, util_get_time()) >= 1) { + send_res = omping_send_client_query(instance, remote_host, + 1); + + ci->last_query_ts = util_get_time(); + } + } else { + send_res = omping_send_client_query(instance, remote_host, 1); + } + break; + case RH_CS_STOP: + /* + * Do nothing + */ + break; + } + + switch (send_res) { + case -1: + err(2, "Cannot send message"); + /* NOTREACHED */ + break; + case -2: + return (-2); + /* NOTREACHED */ + break; + case -3: + warn("Send message error"); + ci->no_err_msgs++; + break; + case -4: + DEBUG_PRINTF("Cannot send message. Buffer too small"); + break; + } + } + + return (0); +} + +/* + * Main loop of omping. It is used for receiving and sending messages. On the end, it prints final + * statistics. instance is omping instance. timeout_time is maximum amount of time to keep loop + * running (after this time, loop is ended). final_stats is boolean flag which determines if final + * statistics should be displayed or not. allow_auto_exit is boolean which if set, allows auto exit + * if every client is in STOP state. + */ +static void +omping_send_receive_loop(struct omping_instance *instance, int timeout_time, int final_stats, + int allow_auto_exit) +{ + struct timeval start_time; + int clients_res; + int loop_end; + int poll_rec_res; + int receive_timeout; + uint64_t time_diff; + + if (timeout_time != 0) { + start_time = util_get_time(); + } + + loop_end = 0; + + do { + clients_res = omping_send_client_msgs(instance); + if (clients_res != 0 && clients_res != -2) { + err(3, "unknown value of clients_res %u", clients_res); + /* NOTREACHED */ + } + + if (clients_res == -2) { + if (exit_requested) { + loop_end = 1; + } + + continue; + } + + if (timeout_time != 0) { + time_diff = util_time_absdiff(start_time, util_get_time()); + + if ((int)time_diff + instance->wait_time > timeout_time) { + receive_timeout = timeout_time - time_diff; + } else { + receive_timeout = instance->wait_time; + } + } else { + receive_timeout = instance->wait_time; + } + + poll_rec_res = omping_poll_receive_loop(instance, receive_timeout); + + if (poll_rec_res != 0 && poll_rec_res != -2) { + err(3, "unknown value of poll_rec_res %u", poll_rec_res); + /* NOTREACHED */ + } + + if (exit_requested) { + loop_end = 1; + } + + if (timeout_time != 0 && + (int)util_time_absdiff(start_time, util_get_time()) >= timeout_time) { + loop_end = 1; + } + + if (allow_auto_exit && instance->auto_exit && instance->rh_no_active == 0) { + loop_end = 1; + } + } while (!loop_end); + + if (final_stats) { + if (instance->op_mode == OMPING_OP_MODE_SHOW_VERSION) { + print_final_remote_version(&instance->remote_hosts, instance->hn_max_len); + } else { + print_final_stats(&instance->remote_hosts, instance->hn_max_len, + instance->transport_method); + } + } +} + +/* + * Print status of client with host_name (maximum length of host_name_len). transport_method is + * transport method to be used, mcast_addr is current multicast address to be used by client. + * remote_addr is address of client and state is current state of client. + */ +static void +print_client_state(const char *host_name, int host_name_len, + enum sf_transport_method transport_method, const struct sockaddr_storage *mcast_addr, + const struct sockaddr_storage *remote_addr, enum rh_client_state state, + enum rh_client_stop_reason stop_reason) +{ + char mcast_addr_str[INET6_ADDRSTRLEN]; + char rh_addr_str[INET6_ADDRSTRLEN]; + + printf("%-*s : ", host_name_len, host_name); + + switch (state) { + case RH_CS_INITIAL: + printf("waiting for response msg"); + break; + case RH_CS_QUERY: + memset(mcast_addr_str, 0, sizeof(mcast_addr_str)); + memset(rh_addr_str, 0, sizeof(rh_addr_str)); + + if (mcast_addr != NULL) { + af_sa_to_str(AF_CAST_SA(mcast_addr), mcast_addr_str); + } + + if (remote_addr != NULL) { + af_sa_to_str(AF_CAST_SA(remote_addr), rh_addr_str); + } + + switch (transport_method) { + case SF_TM_ASM: + printf("joined (S,G) = (*, %s), pinging", mcast_addr_str); + break; + case SF_TM_SSM: + printf("joined (S,G) = (%s, %s), pinging", rh_addr_str, mcast_addr_str); + break; + case SF_TM_IPBC: + printf("joined (S,G) = (*, %s), pinging", mcast_addr_str); + break; + } + break; + case RH_CS_STOP: + switch (stop_reason) { + case RH_CSR_NONE: + DEBUG_PRINTF("internal program error."); + errx(1, "Internal program error"); + break; + case RH_CSR_SERVER: + printf("server told us to stop"); + break; + case RH_CSR_SEND_MAXIMUM: + printf("maximum number of query messages exhausted"); + break; + case RH_CSR_TO_SEND_EXHAUSTED: + printf("given amount of query messages was sent"); + break; + case RH_CSR_REMOTE_VERSION_RECEIVED: + printf("remote version received"); + break; + } + break; + } + printf("\n"); +} + +/* + * Print final remote versions. remote_hosts is list with all remote hosts and host_name_len is + * maximal length of host name in list. + */ +static void +print_final_remote_version(const struct rh_list *remote_hosts, int host_name_len) +{ + struct rh_item *rh_item; + struct rh_item_ci *ci; + size_t i; + unsigned char ch; + + printf("\n"); + + TAILQ_FOREACH(rh_item, remote_hosts, entries) { + ci = &rh_item->client_info; + + printf("%-*s : ", host_name_len, rh_item->addr->host_name); + + if (ci->server_info_len == 0) { + printf("response message not received\n"); + } else { + for (i = 0; i < ci->server_info_len; i++) { + ch = ci->server_info[i]; + + if (ch >= ' ' && ch < 0x7f && ch != '\\') { + fputc(ch, stdout); + } else { + if (ch == '\\') { + printf("\\\\"); + } else { + printf("\\x%02X", ch); + } + } + } + + printf("\n"); + } + } +} + +/* + * Print final statistics. remote_hosts is list with all remote hosts and host_name_len is maximal + * length of host name in list. transport_method is transport method (SF_TM_ASM/SSM/IPBC) from + * omping instance. + */ +static void +print_final_stats(const struct rh_list *remote_hosts, int host_name_len, + enum sf_transport_method transport_method) +{ + const char *cast_str; + struct rh_item *rh_item; + struct rh_item_ci *ci; + enum sf_cast_type cast_type; + double avg_rtt; + int i; + int loss; + int loss_adj; + uint64_t received; + uint64_t sent; + + printf("\n"); + + loss_adj = 0; + + TAILQ_FOREACH(rh_item, remote_hosts, entries) { + for (i = 0; i < 2; i++) { + if (i == 0) { + cast_type = SF_CT_UNI; + } else { + switch (transport_method) { + case SF_TM_ASM: + case SF_TM_SSM: + cast_type = SF_CT_MULTI; + break; + case SF_TM_IPBC: + cast_type = SF_CT_BROAD; + break; + default: + DEBUG_PRINTF("Internal error - unknown transport method"); + errx(1, "Internal error - unknown transport method"); + /* NOTREACHED */ + } + } + + cast_str = sf_cast_type_to_str(cast_type); + ci = &rh_item->client_info; + + received = ci->no_received[i]; + sent = ci->no_sent; + + printf("%-*s : ", host_name_len, rh_item->addr->host_name); + + if (received == 0 && i == 0) { + printf("response message never received\n"); + break; + } + + if (i != 0) { + loss_adj = get_packet_loss_percent(sent - ci->first_mcast_seq + 1, + received); + } + + loss = get_packet_loss_percent(sent, received); + + if (received == 0) { + avg_rtt = 0; + } else { + avg_rtt = ci->avg_rtt[i] / UTIL_NSINMS; + } + + printf("%5scast, ", cast_str); + + printf("xmt/rcv/%%loss = "); + printf("%"PRIu64"/%"PRIu64, sent, received); + + if (ci->no_dups[i] > 0) { + printf("+%"PRIu64, ci->no_dups[i]); + } + + printf("/%d%%", loss); + if (i != 0 && ci->first_mcast_seq > 1) { + printf(" (seq>=%"PRIu32" %d%%)", ci->first_mcast_seq, loss_adj); + } + + printf(", min/avg/max/std-dev = "); + printf("%.3f/%.3f/%.3f/%.3f", ci->rtt_min[i] / UTIL_NSINMS, avg_rtt, + ci->rtt_max[i] / UTIL_NSINMS, + util_ov_std_dev(ci->m2_rtt[i], ci->no_received[i]) / UTIL_NSINMS); + printf("\n"); + } + } +} + +/* + * Print packet statistics. host_name is remote host name with maximal host_name_len length. seq is + * sequence number of packet, is_dup is boolean with information if packet is duplicate or not, + * msg_len is length of message, dist_set is boolean variable with information if dist is set or + * not. dist is distance of packet (how TTL was changed). rtt_set is boolean variable with + * information if rtt (current round trip time) and avg_rtt (average round trip time) is set and + * computed or not. loss is number of lost packets. cast_type is type of packet received + * (unicast/multicast/broadcast). cont_stat is boolean variable saying, if to display + * continuous statistic or not. + */ +static void +print_packet_stats(const char *host_name, int host_name_len, uint32_t seq, int is_dup, + size_t msg_len, int dist_set, uint8_t dist, int rtt_set, double rtt, double avg_rtt, int loss, + enum sf_cast_type cast_type, int cont_stat) +{ + const char *cast_str; + + cast_str = sf_cast_type_to_str(cast_type); + + printf("%-*s : ", host_name_len, host_name); + printf("%5scast, ", cast_str); + printf("seq=%"PRIu32, seq); + + if (is_dup) { + printf(" (dup)"); + } + + printf(", "); + printf("size=%zu bytes", msg_len); + + if (dist_set) { + printf(", dist=%"PRIu8, dist); + } + + if (rtt_set) { + printf(", time=%.3fms", rtt); + } + + if (cont_stat) { + printf(" ("); + + if (rtt_set) { + printf("%.3f avg, ", avg_rtt); + } + + printf("%d%% loss)", loss); + } + + printf("\n"); +} + +/* + * Register global signal handlers for application. sigaction is used to allow *BSD behavior, where + * recvmsg, sendto, ... can return EINTR, what signal (Linux) doesn't do (functions are restarted + * automatically) + */ +static void +register_signal_handlers(void) +{ + struct sigaction act; + + act.sa_handler = sigint_handler; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + + sigaction(SIGINT, &act, NULL); + + act.sa_handler = siginfo_handler; +#ifdef SIGINFO + sigaction(SIGINFO, &act, NULL); +#endif + sigaction(SIGUSR1, &act, NULL); +} + +/* + * Handler for SIGINFO signal + */ +static void +siginfo_handler(int sig) +{ + display_stats_requested++; +} + +/* + * Handler for SIGINT signal + */ +static void +sigint_handler(int sig) +{ + exit_requested++; + + DEBUG2_PRINTF("Exit requested %d times", exit_requested); + + if (exit_requested > MAX_EXIT_REQUESTS) { + signal(SIGINT, SIG_DFL); + kill(getpid(), SIGINT); + } +} diff --git a/omping.h b/omping.h new file mode 100644 index 0000000..09fdb4e --- /dev/null +++ b/omping.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#ifndef _OMPING_H_ +#define _OMPING_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define PROGRAM_NAME "omping" +#define PROGRAM_VERSION "0.0.4" +#define PROGRAM_SERVER_INFO PROGRAM_NAME" "PROGRAM_VERSION + +#define DEFAULT_PORT_S "4321" +#define DEFAULT_MCAST4_ADDR "232.43.211.234" +#define DEFAULT_MCAST6_ADDR "ff3e::4321:1234" + +#define DEFAULT_WAIT_TIME 1000 +#define DEFAULT_TTL 64 + +/* + * Default Wait For Finish multiply constant. wait_time is multiplied with following + * value. + */ +#define DEFAULT_WFF_TIME_MUL 3 + +/* + * Minimum number of elements in duplicate buffer + */ +#define MIN_DUP_BUF_ITEMS 1024 +/* + * Default seconds which must be stored in duplicate buffer. + * This value is divided by ping interval in seconds. If value is smaller + * then MIN_DUP_BUF_ITEMS, then MIN_DUP_BUF_ITEMS is used. + */ +#define DUP_BUF_SECS (2 * 60) + +/* + * Default burst value for rate limit GCRA + */ +#define GCRA_BURST 5 + +/* + * Minimum send and receive socket buffer size + */ +#define MIN_SNDBUF_SIZE 2048 +#define MIN_RCVBUF_SIZE 2048 + +/* + * Protocol version used in messages + */ +#define PROTOCOL_VERSION 2 + +#define MAX_MSG_SIZE 65535 + +enum omping_op_mode { + OMPING_OP_MODE_NORMAL, + OMPING_OP_MODE_CLIENT, + OMPING_OP_MODE_SERVER, + OMPING_OP_MODE_SHOW_VERSION, +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _OMPING_H_ */ diff --git a/omping.spec b/omping.spec new file mode 100644 index 0000000..9e41d41 --- /dev/null +++ b/omping.spec @@ -0,0 +1,45 @@ +Name: omping +Version: 0.0.4 +Release: 1%{?dist} +Summary: Utility to test IP multicast functionality +Group: Applications/Internet +License: ISC +URL: http://fedorahosted.org/omping/ +Source0: http://fedorahosted.org/releases/o/m/omping/%{name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +%description +Omping (Open Multicast Ping) is tool to test IP multicast functionality +primarily in local network. + +%prep +%setup -q + +%build +make %{?_smp_mflags} CFLAGS="%{optflags}" + +%install +rm -rf %{buildroot} +make DESTDIR="%{buildroot}" PREFIX="%{_prefix}" install + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) +%doc AUTHORS COPYING +%{_bindir}/%{name} +%{_mandir}/man8/* + +%changelog +* Mon Jun 22 2011 Jan Friesse - 0.0.4-1 +- Update to version 0.0.4 + +* Mon May 02 2011 Jan Friesse - 0.0.3-1 +- Update to version 0.0.3 + +* Wed Nov 24 2010 Jan Friesse - 0.0.1-2 +- Change hard coded prefix path to macro + +* Fri Nov 19 2010 Jan Friesse - 0.0.1-1 +- Initial package for Fedora diff --git a/rhfunc.c b/rhfunc.c new file mode 100644 index 0000000..20a2cf0 --- /dev/null +++ b/rhfunc.c @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "rhfunc.h" +#include "omping.h" + +/* + * Add item to remote host list. Addr pointer is stored in rh_item. On fail, function returns NULL, + * otherwise newly allocated rh_item is returned. dup_buf_items is number of items to be stored in + * duplicate buffers. rate_limit_time is maximum time between two received packets. + */ +struct rh_item * +rh_list_add_item(struct rh_list *rh_list, struct ai_item *addr, int dup_buf_items, + int rate_limit_time) +{ + struct rh_item *rh_item; + struct rh_item_ci *ci; + int i; + + rh_item = (struct rh_item *)malloc(sizeof(struct rh_item)); + if (rh_item == NULL) { + return (NULL); + } + + memset(rh_item, 0, sizeof(struct rh_item)); + + rh_item->addr = addr; + ci = &rh_item->client_info; + + if (dup_buf_items > 0) { + ci->dup_buf_items = dup_buf_items; + + for (i = 0; i < 2; i++) { + ci->dup_buffer[i] = (uint32_t *)malloc(dup_buf_items * sizeof(uint32_t)); + + if (ci->dup_buffer[i] == NULL) { + goto malloc_error; + } + + memset(ci->dup_buffer[i], 0, dup_buf_items * sizeof(uint32_t)); + } + } + + if (rate_limit_time > 0) { + gcra_init(&rh_item->server_info.gcra, rate_limit_time, GCRA_BURST); + } + + TAILQ_INSERT_TAIL(rh_list, rh_item, entries); + + return (rh_item); + +malloc_error: + for (i = 0; i < 2; i++) { + free(rh_item->client_info.dup_buffer[i]); + } + free(rh_item); + + return (NULL); +} + +/* + * Create list of rh_items. It's also possible to pass ai_list to include every address from list to + * newly allocated rh_list. dup_buf_items is number of items to be stored in duplicate buffers. + * rate_limit_time is maximum time between two received packets. + */ +void +rh_list_create(struct rh_list *rh_list, struct ai_list *remote_addrs, int dup_buf_items, + int rate_limit_time) +{ + struct ai_item *addr; + struct rh_item *rh_item; + + TAILQ_INIT(rh_list); + + if (remote_addrs != NULL) { + TAILQ_FOREACH(addr, remote_addrs, entries) { + rh_item = rh_list_add_item(rh_list, addr, dup_buf_items, rate_limit_time); + if (rh_item == NULL) { + errx(1, "Can't alloc memory"); + } + } + } +} + +/* + * Find remote host with addr sa in list. rh_item pointer is returned on success otherwise NULL is + * returned. + */ +struct rh_item * +rh_list_find(struct rh_list *rh_list, const struct sockaddr *sa) +{ + struct rh_item *rh_item; + + TAILQ_FOREACH(rh_item, rh_list, entries) { + if (af_sockaddr_eq((const struct sockaddr *)&rh_item->addr->sas, sa)) + return (rh_item); + } + + return (NULL); +} + +/* + * Free list from memory. + */ +void +rh_list_free(struct rh_list *rh_list) +{ + struct rh_item *rh_item; + struct rh_item *rh_item_next; + int i; + + rh_item = TAILQ_FIRST(rh_list); + + while (rh_item != NULL) { + rh_item_next = TAILQ_NEXT(rh_item, entries); + + free(rh_item->client_info.server_info); + free(rh_item->client_info.ses_id); + + for (i = 0; i < 2; i++) { + free(rh_item->client_info.dup_buffer[i]); + } + + free(rh_item); + + rh_item = rh_item_next; + } + + TAILQ_INIT(rh_list); +} + +/* + * Generate CID for all items in rh_list + */ +void +rh_list_gen_cid(struct rh_list *rh_list, const struct ai_item *local_addr) +{ + struct rh_item *rh_item; + + TAILQ_FOREACH(rh_item, rh_list, entries) { + util_gen_cid(rh_item->client_info.client_id, local_addr); + } +} + +/* + * Return length of longest host name from rh_list list. + */ +int +rh_list_hn_max_len(struct rh_list *rh_list) +{ + struct rh_item *rh_item; + size_t max_len; + + max_len = 0; + TAILQ_FOREACH(rh_item, rh_list, entries) { + if (strlen(rh_item->addr->host_name) > max_len) { + max_len = strlen(rh_item->addr->host_name); + } + } + + return (max_len > INT_MAX ? INT_MAX : (int)max_len); +} + +/* + * Return number of items in rh_list. + */ +unsigned int +rh_list_length(const struct rh_list *rh_list) +{ + struct rh_item *rh_item; + unsigned int res; + + res = 0; + + TAILQ_FOREACH(rh_item, rh_list, entries) { + res++; + } + + return (res); +} + +/* + * Move all items in rh_list to finish state. fs is which part of remote host is put to finish + * state. This may mean, that server state is put to RH_SS_FINISHING and/or client state is moved + * to RH_CS_STOP + */ +void +rh_list_put_to_finish_state(struct rh_list *rh_list, enum rh_list_finish_state fs) +{ + struct rh_item *rh_item; + + TAILQ_FOREACH(rh_item, rh_list, entries) { + if (fs == RH_LFS_SERVER || fs == RH_LFS_BOTH) { + rh_item->server_info.state = RH_SS_FINISHING; + } + if (fs == RH_LFS_CLIENT || fs == RH_LFS_BOTH) { + rh_item->client_info.state = RH_CS_STOP; + } + } +} diff --git a/rhfunc.h b/rhfunc.h new file mode 100644 index 0000000..6c76892 --- /dev/null +++ b/rhfunc.h @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#ifndef _RHFUNC_H_ +#define _RHFUNC_H_ + +#include + +#include +#include + +#include +#include + +#include "addrfunc.h" +#include "gcra.h" +#include "util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum rh_client_state { + RH_CS_INITIAL, + RH_CS_QUERY, + RH_CS_STOP +}; + +enum rh_server_state { + RH_SS_INITIAL, + RH_SS_ANSWER, + RH_SS_FINISHING, +}; + +enum rh_client_stop_reason { + RH_CSR_NONE, + RH_CSR_SERVER, + RH_CSR_TO_SEND_EXHAUSTED, + RH_CSR_SEND_MAXIMUM, + RH_CSR_REMOTE_VERSION_RECEIVED, +}; + +enum rh_list_finish_state { + RH_LFS_CLIENT, + RH_LFS_SERVER, + RH_LFS_BOTH, +}; + +/* + * Remote host info item, client info part + */ +struct rh_item_ci { + enum rh_client_state state; + char client_id[CLIENTID_LEN]; + struct timeval last_init_ts; + struct timeval last_query_ts; + char *server_info; + char *ses_id; + uint32_t *dup_buffer[2]; + size_t server_info_len; + size_t ses_id_len; + double avg_rtt[2]; + double m2_rtt[2]; + double rtt_max[2]; + double rtt_min[2]; + uint64_t no_err_msgs; + uint64_t no_dups[2]; + uint64_t no_received[2]; + uint64_t no_sent; + uint32_t first_mcast_seq; + uint32_t lru_seq_num; /* Last Received Unicast seq number */ + uint32_t seq_num; + int dup_buf_items; + int seq_num_overflow; +}; + +/* + * Remote host info item, server info part + */ +struct rh_item_si { + enum rh_server_state state; + char ses_id[SESSIONID_LEN]; + struct gcra_item gcra; + struct timeval last_init_ts; +}; + +/* + * Remote host info item. This is intended to use with TAILQ list. + */ +struct rh_item { + struct ai_item *addr; + struct rh_item_ci client_info; + struct rh_item_si server_info; + TAILQ_ENTRY(rh_item) entries; +}; + +/* + * Typedef of TAILQ head of list of rh_item(s) + */ +TAILQ_HEAD(rh_list, rh_item); + +extern struct rh_item *rh_list_add_item(struct rh_list *rh_list, struct ai_item *addr, + int dup_buf_items, int rate_limit_time); + +extern void rh_list_create(struct rh_list *rh_list, struct ai_list *remote_addrs, + int dup_buf_items, int rate_limit_time); + +extern struct rh_item *rh_list_find(struct rh_list *rh_list, const struct sockaddr *sa); +extern void rh_list_free(struct rh_list *rh_list); + +extern void rh_list_gen_cid(struct rh_list *rh_list, + const struct ai_item *local_addr); + +extern int rh_list_hn_max_len(struct rh_list *rh_list); + +extern unsigned int rh_list_length(const struct rh_list *rh_list); + +extern void rh_list_put_to_finish_state(struct rh_list *rh_list, + enum rh_list_finish_state fs); + +#ifdef __cplusplus +} +#endif + +#endif /* _RHFUNC_H_ */ diff --git a/rsfunc.c b/rsfunc.c new file mode 100644 index 0000000..a596e52 --- /dev/null +++ b/rsfunc.c @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "addrfunc.h" +#include "logging.h" +#include "rsfunc.h" +#include "util.h" + +/* + * Wrapper on top of poll. This poll stores old timestamp so it's possible to put always same + * timeout but correct timeout is computed from old_tstamp and current time. In other words, this + * function will always after timeout expire return timeout (0) not depending on number of times + * this function was called. + * unicast_socket and multicast_socket are two sockets, timeout is absolute timeout (after this + * value, function returns 0) and old_tstamp is internal state variable (on first call value + * must be zeroed). + * Function return bit field (unicast_socket - bit 1, multicast_socket - bit 2) if something was + * read, 0 on timeout, -1 on fail (use errno) and -2 on interrupt. + */ +int +rs_poll_timeout(int unicast_socket, int multicast_socket, int timeout, struct timeval *old_tstamp) +{ + struct pollfd pfds[2]; + struct timeval cur_time; + int poll_timeout; + int poll_res; + int res; + + cur_time = util_get_time(); + + if (old_tstamp->tv_sec == 0 && old_tstamp->tv_usec == 0) { + *old_tstamp = cur_time; + } + + if ((int)util_time_absdiff(cur_time, *old_tstamp) > timeout) { + memset(old_tstamp, 0, sizeof(*old_tstamp)); + + return (0); + } + + poll_timeout = timeout - util_time_absdiff(cur_time, *old_tstamp); + if (poll_timeout < 0) { + poll_timeout = 0; + } + + memset(pfds, 0, sizeof(struct pollfd) * 2); + + pfds[0].fd = unicast_socket; + pfds[0].events = POLLIN; + + pfds[1].fd = multicast_socket; + pfds[1].events = POLLIN; + + poll_res = poll(pfds, 2, poll_timeout); + + if (poll_res == 0) { + memset(old_tstamp, 0, sizeof(*old_tstamp)); + + return (0); + } + + if (poll_res == -1) { + if (errno == EINTR) { + DEBUG2_PRINTF("poll error - EINTR"); + return (-2); + } else { + DEBUG2_PRINTF("poll error - errno = %d", errno); + return (-1); + } + } + + if (pfds[0].revents & POLLERR || pfds[0].revents & POLLHUP || pfds[0].revents & POLLNVAL) { + DEBUG2_PRINTF("poll error. pfds[0] revents = %d", pfds[0].revents); + return (-1); + } + + if (pfds[1].revents & POLLERR || pfds[1].revents & POLLHUP || pfds[1].revents & POLLNVAL) { + DEBUG2_PRINTF("poll error. pfds[1] revents = %d", pfds[1].revents); + return (-1); + } + + res = 0; + if (pfds[0].revents & POLLIN) { + res |= 1; + } + + if (pfds[1].revents & POLLIN) { + res |= 2; + } + + return (res); +} + +/* + * Wrapper on top of recvmsg which emulates recvfrom but it's also able to return ttl. sock is + * socket where to make recvmsg. from_addr is address where address of source will be stored. msg is + * buffer where to store message with maximum msg_len size. ttl is pointer where TTL (time-to-live) + * from packet will be stored (or 0 if no such information is available). Timestamp is filled + * either by SCM_TIMESTAMP directly from packet (if supported) or current get gettimeofday. + * NULL can be passed as timestamp pointer. + * Return number of received bytes, or -2 on EINTR, -3 on one of EHOSTUNREACH | ENETDOWN | + * EHOSTDOWN | ECONNRESET, -4 if message is truncated, or -1 on different error. + */ +ssize_t +rs_receive_msg(int sock, struct sockaddr_storage *from_addr, char *msg, size_t msg_len, + uint8_t *ttl, struct timeval *timestamp) +{ + char cmsg_buf[CMSG_SPACE(1024)]; + struct cmsghdr *cmsg; + struct iovec msg_iovec; + struct msghdr msg_hdr; + ssize_t recv_size; + int ittl; + int timestamp_set; + + ittl = 0; + timestamp_set = 0; + + memset(&msg_iovec, 0, sizeof(msg_iovec)); + msg_iovec.iov_base = msg; + msg_iovec.iov_len = msg_len; + + memset(&msg_hdr, 0, sizeof(msg_hdr)); + msg_hdr.msg_name = from_addr; + msg_hdr.msg_namelen = sizeof(struct sockaddr_storage); + msg_hdr.msg_iov = &msg_iovec; + msg_hdr.msg_iovlen = 1; + msg_hdr.msg_control = cmsg_buf; + msg_hdr.msg_controllen = sizeof(cmsg_buf); + + recv_size = recvmsg(sock, &msg_hdr, 0); + + if (recv_size == -1) { + if (errno == EINTR) { + DEBUG2_PRINTF("recvmsg error - EINTR"); + return (-2); + } + + if (errno == EHOSTUNREACH || errno == EHOSTDOWN || errno == ENETDOWN || + errno == ECONNRESET) { + DEBUG2_PRINTF("recvmsg error - EHOSTUNREACH || EHOSTDOWN || ENETDOWN ||" + " ECONNRESET"); + return (-3); + } + + DEBUG2_PRINTF("recvmsg error - errno = %d", errno); + return (-1); + } + + if (msg_hdr.msg_flags & MSG_TRUNC || msg_hdr.msg_flags & MSG_CTRUNC) { + DEBUG2_PRINTF("recvmsg error - MSG_TRUNC | MSG_CTRUNC"); + return (-4); + } + + for (cmsg = CMSG_FIRSTHDR(&msg_hdr); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg_hdr, cmsg)) { + switch (cmsg->cmsg_level) { + case SOL_SOCKET: +#ifdef SCM_TIMESTAMP + if (cmsg->cmsg_type == SCM_TIMESTAMP && + cmsg->cmsg_len >= sizeof(struct timeval) && timestamp != NULL) { + memcpy(timestamp, CMSG_DATA(cmsg), sizeof(struct timeval)); + timestamp_set = 1; + } +#endif + case IPPROTO_IP: + if (cmsg->cmsg_type == IP_TTL && cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { + memcpy(&ittl, CMSG_DATA(cmsg), sizeof(ittl)); + } +#ifdef IP_RECVTTL + if (cmsg->cmsg_type == IP_RECVTTL && cmsg->cmsg_len > 1) { + ittl = *(uint8_t *)CMSG_DATA(cmsg); + } +#endif + break; + case IPPROTO_IPV6: + if (cmsg->cmsg_type == IPV6_HOPLIMIT && cmsg->cmsg_len == + CMSG_LEN(sizeof(int))) { + memcpy(&ittl, CMSG_DATA(cmsg), sizeof(ittl)); + } + break; + } + } + + *ttl = (uint8_t)ittl; + + if (!timestamp_set && timestamp != NULL) { + *timestamp = util_get_time(); + } + + return (recv_size); +} + +/* + * Thin wrapper on top of sendto. sock is socket, msg is message with msg_size length to send and to + * is address where to send message. + * Return number of sent bytes or -2 on EINTR, -3 on one of EHOSTDOWN | ENETDOWN | EHOSTUNREACH | + * ENOBUFS or -1 on some different error (sent != msg_size). + */ +ssize_t +rs_sendto(int sock, const char *msg, size_t msg_size, const struct sockaddr_storage *to) +{ + ssize_t sent; + + sent = sendto(sock, msg, msg_size, 0, (struct sockaddr *)to, af_sas_len(to)); + + if (sent == -1) { + if (errno == EINTR) { + DEBUG2_PRINTF("sendto error - EINTR"); + return (-2); + } + + if (errno == EHOSTUNREACH || errno == EHOSTDOWN || errno == ENETDOWN || + errno == ENOBUFS) { + DEBUG2_PRINTF("sendto error - EHOSTUNREACH || EHOSTDOWN || ENETDOWN ||" + "ENOBUFS"); + return (-3); + } + + DEBUG2_PRINTF("sendto error - errno = %d", errno); + return (-1); + } + + if ((size_t)sent != msg_size) { + DEBUG2_PRINTF("sendto error - sent != msg_size"); + + return (-1); + } + + return (sent); +} diff --git a/rsfunc.h b/rsfunc.h new file mode 100644 index 0000000..7c4bd9a --- /dev/null +++ b/rsfunc.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#ifndef _RSFUNC_H_ +#define _RSFUNC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +extern int rs_poll_timeout(int unicast_socket, int multicast_socket, int timeout, + struct timeval *old_tstamp); + +extern ssize_t rs_receive_msg(int sock, struct sockaddr_storage *from_addr, char *msg, + size_t msg_len, uint8_t *ttl, struct timeval *timestamp); + +extern ssize_t rs_sendto(int sock, const char *msg, size_t msg_size, + const struct sockaddr_storage *to); + +#ifdef __cplusplus +} +#endif + +#endif /* _RSFUNC_H_ */ diff --git a/sockfunc.c b/sockfunc.c new file mode 100644 index 0000000..3344ee3 --- /dev/null +++ b/sockfunc.c @@ -0,0 +1,889 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "addrfunc.h" +#include "logging.h" +#include "sockfunc.h" + +static int sf_set_socket_common_options(int sock, const struct sockaddr *addr, + enum sf_cast_type cast_type, uint8_t ttl, int force_recvttl, int receive_timestamp, + int sndbuf_size, int rcvbuf_size, int force_buf_size); + +/* + * Bind socket sock to given address bind_addr. + * Function returns 0 on success, otherwise -1. + */ +int +sf_bind_socket(const struct sockaddr *bind_addr, int sock) +{ + if (bind(sock, bind_addr, af_sa_len(bind_addr)) == -1) { + DEBUG_PRINTF("Can't bind socket"); + + return (-1); + } + + return (0); +} + +/* + * Return cast_type converted to string (uni/multi/broad). + */ +const char * +sf_cast_type_to_str(enum sf_cast_type cast_type) +{ + const char *res; + + switch (cast_type) { + case SF_CT_UNI: + res = "uni"; + break; + case SF_CT_MULTI: + res = "multi"; + break; + case SF_CT_BROAD: + res = "broad"; + break; + default: + DEBUG_PRINTF("Internal error - unknown transport method"); + errx(1, "Internal error - unknown transport method"); + /* NOTREACHED */ + } + + return (res); +} + +/* + * Create and bind UDP multicast/broadcast socket. + * Socket is created with mcast_addr address, joined to local_addr address on local_ifname NIC + * interface with ttl Time-To-Live. + * allow_mcast_loop is boolean flag to set mcast_loop. + * transport_method is transport method to use. + * remote_addrs are list of remote addresses of ai_list type. This is used for SSM to join into + * appropriate source groups. If receive_timestamp is set, recvmsg cmsg will (if supported) + * contain timestamp of packet receive. + * force_recv_ttl is used to force set of recvttl (if option is not supported, + * error is returned). sndbuf_size is size of socket buffer to allocate for sending packets. + * rcvbuf_size is size of socket buffer to allocate for receiving packets. + * bind_port is port to bind. It can be 0 and then port from mcast_addr is used. + * Return -1 on failure, otherwise socket file descriptor is returned. + */ +int +sf_create_multicast_socket(const struct sockaddr *mcast_addr, const struct sockaddr *local_addr, + const char *local_ifname, uint8_t ttl, int allow_mcast_loop, + enum sf_transport_method transport_method, const struct ai_list *remote_addrs, + int receive_timestamp, int force_recvttl, int sndbuf_size, int rcvbuf_size, uint16_t bind_port) +{ +#ifdef __CYGWIN__ + struct sockaddr_storage any_sas; +#endif + struct sockaddr_storage bind_addr; + int sock; + enum sf_cast_type cast_type; + + sock = sf_create_udp_socket(mcast_addr); + if (sock == -1) { + return (-1); + } + + switch (transport_method) { + case SF_TM_ASM: + case SF_TM_SSM: + cast_type = SF_CT_MULTI; + break; + case SF_TM_IPBC: + cast_type = SF_CT_BROAD; + break; + default: + DEBUG_PRINTF("Internal error - unknown transport method"); + errx(1, "Internal error - unknown transport method"); + /* NOTREACHED */ + } + + if (sf_set_socket_common_options(sock, mcast_addr, cast_type, ttl, force_recvttl, + receive_timestamp, sndbuf_size, rcvbuf_size, 1) == -1) { + return (-1); + } + + if (sf_set_socket_reuse(sock) == -1) { + return (-1); + } + + af_copy_sa_to_sas(&bind_addr, mcast_addr); + if (bind_port != 0) { + af_sa_set_port(AF_CAST_SA(&bind_addr), bind_port); + } + + switch (transport_method) { + case SF_TM_ASM: + case SF_TM_SSM: +#ifdef __CYGWIN__ + af_sa_to_any_addr(AF_CAST_SA(&any_sas), AF_CAST_SA(&bind_addr)); + memcpy(&bind_addr, &any_sas, sizeof(*any_sas)); +#endif + + if (sf_bind_socket(AF_CAST_SA(&bind_addr), sock) == -1) { + return (-1); + } + + if (sf_set_socket_mcast_loop(mcast_addr, sock, allow_mcast_loop) == -1) { + return (-1); + } + + break; + case SF_TM_IPBC: + if (sf_bind_socket(AF_CAST_SA(&bind_addr), sock) == -1) { + return (-1); + } + break; + } + + + switch (transport_method) { + case SF_TM_ASM: + if (sf_mcast_join_asm_group(mcast_addr, local_addr, local_ifname, sock) == -1) { + return (-1); + } + break; + case SF_TM_SSM: + if (sf_mcast_join_ssm_group_list(mcast_addr, local_addr, remote_addrs, + local_ifname, sock) == -1) { + return (-1); + } + break; + case SF_TM_IPBC: + /* + * Broadcast packet doesn't need any special handling on receiver side + */ + break; + } + + return (sock); +} + +/* + * Create UDP socket with family from sa. + * Return -1 on failure, otherwise socket file descriptor is returned. + */ +int +sf_create_udp_socket(const struct sockaddr *sa) +{ + int sock; + + sock = socket(sa->sa_family, SOCK_DGRAM, 0); + if (sock == -1) { + DEBUG_PRINTF("Can't create socket"); + return (-1); + } + + return (sock); +} + +/* + * Create and bind UDP unicast socket with ttl Time-To-Live. It can also set multicast ttl if + * set_mcast_ttl not 0. If mcast_send is set, options for sending multicast/broadcast packets are + * set. allow_mcast_loop is boolean flag to set mcast_loop. local_ifname is name of local interface + * where local_addr is present. transport_method is transport method to use. If receive_timestamp is + * set, recvmsg cmsg will (if supported) contain timestamp of packet receive. force_recv_ttl is + * used to force set of recvttl (if option is not supported, error is returned). sndbuf_size is + * size of socket buffer to allocate for sending packets. rcvbuf_size is size of socket buffer + * to allocate for receiving packets. bind_port is port to bind. It can be set to NULL, and then + * port from local_addr is used. If real pointer is used, and value is 0, random port is choosen and + * real port is returned there. Other value will bind port to given value. Port is in network + * format. + * Return -1 on failure, otherwise socket file descriptor is returned. + */ +int +sf_create_unicast_socket(const struct sockaddr *local_addr, uint8_t ttl, int mcast_send, + int allow_mcast_loop, const char *local_ifname, enum sf_transport_method transport_method, + int receive_timestamp, int force_recvttl, int sndbuf_size, int rcvbuf_size, + uint16_t *bind_port) +{ + struct sockaddr_storage bind_addr; + socklen_t bind_addr_len; + int sock; + + sock = sf_create_udp_socket(local_addr); + if (sock == -1) { + return (-1); + } + + if (sf_set_socket_common_options(sock, local_addr, SF_CT_UNI, ttl, force_recvttl, + receive_timestamp, sndbuf_size, rcvbuf_size, 1) == -1) { + return (-1); + } + + if (mcast_send) { + switch (transport_method) { + case SF_TM_ASM: + case SF_TM_SSM: + if (sf_set_socket_ttl(local_addr, SF_CT_MULTI, sock, ttl) == -1) { + return (-1); + } + + if (sf_set_socket_mcast_loop(local_addr, sock, allow_mcast_loop) == -1) { + return (-1); + } + + if (sf_set_socket_mcast_if(local_addr, sock, local_ifname) == -1) { + return (-1); + } + break; + case SF_TM_IPBC: + if (sf_set_socket_broadcast(sock, 1) == -1) { + return (-1); + } + break; + } + } + + af_copy_sa_to_sas(&bind_addr, local_addr); + + if (bind_port != NULL) { + af_sa_set_port(AF_CAST_SA(&bind_addr), *bind_port); + } + + if (sf_bind_socket(AF_CAST_SA(&bind_addr), sock) == -1) { + return (-1); + } + + if (bind_port != NULL && *bind_port == 0) { + bind_addr_len = sizeof(bind_addr); + + if (getsockname(sock, AF_CAST_SA(&bind_addr), &bind_addr_len) == -1) { + return (-1); + } + + *bind_port = af_sa_port(AF_CAST_SA(&bind_addr)); + } + + return (sock); +} + +/* + * Return 1 if broadcast is supported on given OS on compilation time, otherwise 0 + */ +int +sf_is_ipbc_supported(void) +{ +#ifdef __CYGWIN__ + return (0); +#endif + +#ifndef SO_BROADCAST + return (0); +#endif + + return (1); +} + +/* + * Return 1 if ssm is supported on given OS on compilation time, otherwise 0 + */ +int +sf_is_ssm_supported(void) +{ +#if defined (IP_ADD_SOURCE_MEMBERSHIP) || defined (MCAST_JOIN_SOURCE_GROUP) + return (1); +#else + return (0); +#endif +} + +/* + * Join socket to multicast group (ASM). mcast_addr is multicast address, local_address is address + * of local interface to join on, local_ifname is name of interface with local_address address and + * sock is socket to use. + * Function returns 0 on success, otherwise -1. + */ +int +sf_mcast_join_asm_group(const struct sockaddr *mcast_addr, const struct sockaddr *local_addr, + const char *local_ifname, int sock) +{ + struct ip_mreq mreq4; + struct ipv6_mreq mreq6; + int iface_index; + + switch (mcast_addr->sa_family) { + case AF_INET: + memset(&mreq4, 0, sizeof(mreq4)); + + mreq4.imr_multiaddr = ((struct sockaddr_in *)mcast_addr)->sin_addr; + mreq4.imr_interface = ((struct sockaddr_in *)local_addr)->sin_addr; + if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq4, sizeof(mreq4)) == -1) { + DEBUG_PRINTF("setsockopt IP_ADD_MEMBERSHIP failed"); + + return (-1); + } + break; + case AF_INET6: + iface_index = if_nametoindex(local_ifname); + if (iface_index == 0) { + DEBUG_PRINTF("if_nametoindex cannot convert iface name %s to index", + local_ifname); + + return (-1); + } + memset(&mreq6, 0, sizeof(mreq6)); + + mreq6.ipv6mr_multiaddr = ((struct sockaddr_in6 *)mcast_addr)->sin6_addr; + mreq6.ipv6mr_interface = iface_index; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6, sizeof(mreq6)) == -1) { + DEBUG_PRINTF("setsockopt IPV6_JOIN_GROUP failed"); + + return (-1); + } + break; + default: + DEBUG_PRINTF("Unknown sockaddr family"); + errx(1, "Unknown sockaddr family"); + } + + return (0); +} + +/* + * Join socket to multicast group (SSM). mcast_addr is multicast address, local_address is address + * of local interface to join on, remote_addr is used for source of multicast, local_ifname + * is name of interface with local_address address and sock is socket to use. + * Function returns 0 on success, otherwise -1. + */ +int +sf_mcast_join_ssm_group(const struct sockaddr *mcast_addr, const struct sockaddr *local_addr, + const struct sockaddr *remote_addr, const char *local_ifname, int sock) +{ +#ifdef IP_ADD_SOURCE_MEMBERSHIP + struct ip_mreq_source mreq4; +#endif +#ifdef MCAST_JOIN_SOURCE_GROUP + struct group_source_req greq; + size_t addr_len; + int iface_index; + int ip_lv; +#endif + +#ifdef IP_ADD_SOURCE_MEMBERSHIP + if (mcast_addr->sa_family == AF_INET) { + memset(&mreq4, 0, sizeof(mreq4)); + + mreq4.imr_multiaddr = ((struct sockaddr_in *)mcast_addr)->sin_addr; + mreq4.imr_interface = ((struct sockaddr_in *)local_addr)->sin_addr; + mreq4.imr_sourceaddr = ((struct sockaddr_in *)remote_addr)->sin_addr; + + if (setsockopt(sock, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, &mreq4, + sizeof(mreq4)) == -1) { + DEBUG_PRINTF("setsockopt IP_ADD_SOURCE_MEMBERSHIP failed"); + + return (-1); + } + + return (0); + } +#endif + +#ifdef MCAST_JOIN_SOURCE_GROUP + if (mcast_addr->sa_family == AF_INET || mcast_addr->sa_family == AF_INET6) { + iface_index = if_nametoindex(local_ifname); + if (iface_index == 0) { + DEBUG_PRINTF("if_nametoindex cannot convert iface name %s to index", + local_ifname); + + return (-1); + } + + memset(&greq, 0, sizeof(greq)); + + switch (mcast_addr->sa_family) { + case AF_INET: + addr_len = sizeof(struct sockaddr_in); + ip_lv = IPPROTO_IP; + break; + case AF_INET6: + addr_len = sizeof(struct sockaddr_in6); + ip_lv = IPPROTO_IPV6; + break; + default: + DEBUG_PRINTF("Unknown sockaddr family"); + errx(1, "Unknown sockaddr family"); + /* NOTREACHED */ + } + + greq.gsr_interface = iface_index; + memcpy(&greq.gsr_group, mcast_addr, addr_len); + memcpy(&greq.gsr_source, remote_addr, addr_len); + + if (setsockopt(sock, ip_lv, MCAST_JOIN_SOURCE_GROUP, &greq, sizeof(greq)) == -1) { + DEBUG_PRINTF("setsockopt MCAST_JOIN_SOURCE_GROUP failed"); + + return (-1); + } + + return (0); + } +#endif + DEBUG_PRINTF("Can't join to Source-Specific Multicast because of no compile time support"); + errx(1, "Can't join to Source-Specific Multicast because of no compile time support"); + /* NOTREACHED */ + + return (-1); +} + +/* + * Join socket to multicast group (SSM). mcast_addr is multicast address, local_address is address + * of local interface to join on, remote_addrs is used for source of multicast, local_ifname + * is name of interface with local_address address and sock is socket to use. + * Function returns 0 on success, otherwise -1. + */ +int +sf_mcast_join_ssm_group_list(const struct sockaddr *mcast_addr, const struct sockaddr *local_addr, + const struct ai_list *remote_addrs, const char *local_ifname, int sock) +{ + struct ai_item *ai_item_i; + + TAILQ_FOREACH(ai_item_i, remote_addrs, entries) { + if (sf_mcast_join_ssm_group(mcast_addr, local_addr, + (const struct sockaddr *)&ai_item_i->sas, local_ifname, sock) == -1) { + return (-1); + } + } + + return (0); +} + +/* + * Set buffer size for socket sock. snd_buf is boolean which if set, send buffer is modified, + * otherwise receive buffer is modified. buf_size is size of buffer to allocate. This can be <=0 and + * then buffer is left unchanged. new_buf_size is real size provided by OS. new_buf_size also + * accepts NULL as pointer, if information about new buffer size is not needed. if force_buf_size is + * set and OS will not provide enough buffer, error code is returned and errno is set to ENOBUFS + * (this is emulation of *BSD behavior). + * On success 0 is returned, otherwise -1. + */ +int +sf_set_socket_buf_size(int sock, int snd_buf, int buf_size, int *new_buf_size, int force_buf_size) +{ + const char *opt_name_s; + socklen_t optlen; + int opt_name; + int res; + int tmp_buf_size; + + if (snd_buf) { + opt_name = SO_SNDBUF; + opt_name_s = "SO_SNDBUF"; + } else { + opt_name = SO_RCVBUF; + opt_name_s = "SO_RCVBUF"; + } + + if (buf_size > 0) { + res = setsockopt(sock, SOL_SOCKET, opt_name, &buf_size, sizeof(buf_size)); + + if (res == -1) { + DEBUG_PRINTF("setsockopt %s failed", opt_name_s); + + return (-1); + } + } + + if (new_buf_size == NULL && !force_buf_size) { + return (0); + } + + optlen = sizeof(tmp_buf_size); + res = getsockopt(sock, SOL_SOCKET, opt_name, &tmp_buf_size, &optlen); + + if (res == -1) { + DEBUG_PRINTF("getsockopt %s failed", opt_name_s); + + return (-1); + } + + if (force_buf_size && tmp_buf_size < buf_size) { + VERBOSE_PRINTF("Buffer size request was %u bytes, but only %u" + " bytes was allocated", buf_size, tmp_buf_size); + errno = ENOBUFS; + return (-1); + } + + if (new_buf_size != NULL) { + *new_buf_size = tmp_buf_size; + } + + return (0); +} + +/* + * Set common options for socket. Options are ipv6only, ttl, recvttl and receive timestamp. sock is + * socket to set options, addr is address, cast_type is ether uni/multi or broad cast socket. + * ttl is new Time-To-Live. force_recv_ttl is used to force set of recvttl (if option is + * not supported, error is returned). If receive_timestamp is set, recvmsg cmsg will (if + * supported) contain timestamp of packet receive. sndbuf_size is size of socket buffer to + * allocate for sending packets. rcvbuf_size is size of socket buffer to allocate for receiving + * packets. if force_buf_size is set and OS will not provide enough buffer, error code is returned + * and errno is set to ENOBUFS (this is emulation of *BSD behavior). + * Return -1 on failure, otherwise 0. + */ +static int +sf_set_socket_common_options(int sock, const struct sockaddr *addr, enum sf_cast_type cast_type, + uint8_t ttl, int force_recvttl, int receive_timestamp, int sndbuf_size, int rcvbuf_size, + int force_buf_size) +{ + const char *cast_str; + int new_buf_size; + int res; + + cast_str = sf_cast_type_to_str(cast_type); + + if (sf_set_socket_buf_size(sock, 1, sndbuf_size, &new_buf_size, force_buf_size) == -1) { + return (-1); + } + + DEBUG_PRINTF("Send buffer (%scast socket) allocated %u bytes", cast_str, new_buf_size); + + if (sf_set_socket_buf_size(sock, 0, rcvbuf_size, &new_buf_size, force_buf_size) == -1) { + return (-1); + } + + DEBUG_PRINTF("Receive buffer (%scast socket) allocated %u bytes", cast_str, new_buf_size); + + if (addr->sa_family == AF_INET6) { + if (sf_set_socket_ipv6only(addr, sock) == -1) { + return (-1); + } + } + + if (sf_set_socket_ttl(addr, cast_type, sock, ttl) == -1) { + return (-1); + } + + res = sf_set_socket_recvttl(addr, sock); + if (res == -1 || (res == -2 && force_recvttl)) { + return (-1); + } + + if (receive_timestamp) { + if (sf_set_socket_timestamp(sock) == -1) { + return (-1); + } + } + + return (0); +} + +/* + * Enable or disable broadcast sending + * Function returns 0 on success, otherwise -1. + */ +int +sf_set_socket_broadcast(int sock, int enable) +{ + int opt; + + opt = (enable ? 1 : 0); + + if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt)) == -1) { + DEBUG_PRINTF("setsockopt SO_BROADCAST failed"); + + return (-1); + } + + return (0); +} + +/* + * Set ipv6 only flag to socket. Function works only for socket with family AF_INET6. + * Function returns 0 on success, otherwise -1. + */ +int +sf_set_socket_ipv6only(const struct sockaddr *sa, int sock) +{ + int opt; + + opt = 1; + + if (sa->sa_family != AF_INET6) { + return (-1); + } + +#ifdef IPV6_V6ONLY + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) == -1) { + DEBUG_PRINTF("setsockopt IPV6_V6ONLY failed"); + + return (-1); + } +#endif + + return (0); +} + +/* + * Set interface to use for sending multicast packets. local_addr is interface from which packets + * will be send. sock is socket to set option and local_ifname is name of interface with local_addr + * address. + * Function returns 0 on success, otherwise -1. + */ +int +sf_set_socket_mcast_if(const struct sockaddr *local_addr, int sock, const char *local_ifname) +{ + int iface_index; + + switch (local_addr->sa_family) { + case AF_INET: + if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, + &((struct sockaddr_in *)local_addr)->sin_addr, sizeof(struct in_addr)) == -1) { + DEBUG_PRINTF("setsockopt IP_MULTICAST_IF failed"); + + return (-1); + } + break; + case AF_INET6: + iface_index = if_nametoindex(local_ifname); + if (iface_index == 0) { + DEBUG_PRINTF("if_nametoindex cannot convert iface name %s to index", + local_ifname); + + return (-1); + } + + if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, &iface_index, + sizeof(iface_index)) == -1) { + DEBUG_PRINTF("setsockopt IPV6_MULTICAST_IF failed"); + + return (-1); + } + break; + + default: + DEBUG_PRINTF("Unknown sockaddr family"); + errx(1, "Unknown sockaddr family"); + } + + return (0); +} + +/* + * Enables or disables multicast loop on socket. mcast_addr is sockadddr used for address family. + * sock is socket to set and enable should be set to 0 for disable of multicast loop, other values + * means enable. + * Function returns 0 on success, otherwise -1. + */ +int +sf_set_socket_mcast_loop(const struct sockaddr *mcast_addr, int sock, int enable) +{ + uint8_t val; + int ival; + + val = (enable ? 1 : 0); + ival = val; + + switch (mcast_addr->sa_family) { + case AF_INET: + if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &val, sizeof(val)) == -1) { + DEBUG_PRINTF("setsockopt IP_MULTICAST_LOOP failed"); + + return (-1); + } + break; + case AF_INET6: + if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &ival, + sizeof(ival)) == -1) { + DEBUG_PRINTF("setsockopt IPV6_MULTICAST_LOOP failed"); + + return (-1); + } + break; + default: + DEBUG_PRINTF("Unknown sockaddr family"); + errx(1, "Unknown sockaddr family"); + } + + return (0); +} + +/* + * Set option to receive TTL inside packet information (recvmsg). sa is sockaddr used for address + * family and sock is socket to use. + * Function returns 0 on success. -2 is returned on systems, where IP_RECVTTL is not available, + * otherwise -1 is returned. + */ +int +sf_set_socket_recvttl(const struct sockaddr *sa, int sock) +{ + int opt; + + opt = 1; + + switch (sa->sa_family) { + case AF_INET: +#ifdef IP_RECVTTL + if (setsockopt(sock, IPPROTO_IP, IP_RECVTTL, &opt, sizeof(opt)) == -1) { + DEBUG_PRINTF("setsockopt IP_RECVTTL failed"); + + return (-1); + } +#else + return (-2); +#endif + break; + case AF_INET6: +#ifdef IPV6_RECVHOPLIMIT + if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &opt, sizeof(opt)) == -1) { + DEBUG_PRINTF("setsockopt IPV6_RECVHOPLIMIT failed"); + + return (-1); + } +#else + if (setsockopt(sock, IPPROTO_IPV6, IPV6_HOPLIMIT, &opt, sizeof(opt)) == -1) { + DEBUG_PRINTF("setsockopt IPV6_HOPLIMIT failed"); + + return (-1); + } +#endif + break; + default: + DEBUG_PRINTF("Unknown sockaddr family"); + errx(1, "Unknown sockaddr family"); + } + + + return (0); +} + +/* + * Set reuse of address on socket sock. + * Function returns 0 on success, otherwise -1. + */ +int +sf_set_socket_reuse(int sock) +{ + int opt; + + opt = 1; + + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) { + DEBUG_PRINTF("setsockopt SO_REUSEADDR failed"); + + return (-1); + } + +#ifdef SO_REUSEPORT + if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)) == -1) { + DEBUG_PRINTF("setsockopt SO_REUSEPORT failed"); + + return (-1); + } +#endif + + return (0); +} + +/* + * Enable receiving of timestamp for socket. + * Function returns 0 on success, otherwise -1. + */ +int +sf_set_socket_timestamp(int sock) +{ +#ifdef SO_TIMESTAMP + int opt; + + opt = 1; + + if (setsockopt(sock, SOL_SOCKET, SO_TIMESTAMP, &opt, sizeof(opt)) == -1) { + DEBUG_PRINTF("setsockopt SO_TIMESTAMP failed"); + + return (-1); + } +#endif + + return (0); +} + +/* + * Set TTL (time-to-live) to socket. sa is sockaddr used to determine address family, cast_type is + * variable used to determine if socket is unicast, multicast or broadcast and ttl is actual + * TTL to set. + * Function returns 0 on success, otherwise -1. + */ +int +sf_set_socket_ttl(const struct sockaddr *sa, enum sf_cast_type cast_type, int sock, uint8_t ttl) +{ + int ittl; + int res; + + ittl = ttl; + + switch (sa->sa_family) { + case AF_INET: + if (cast_type == SF_CT_MULTI) { + res = setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); + if (res == -1) { + DEBUG_PRINTF("setsockopt IP_MULTICAST_TTL failed"); + return (-1); + } + } else { + res = setsockopt(sock, IPPROTO_IP, IP_TTL, &ittl, sizeof(ittl)); + if (res == -1) { + DEBUG_PRINTF("setsockopt IP_TTL failed"); + return (-1); + } + } + break; + case AF_INET6: + if (cast_type == SF_CT_MULTI) { + res = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ittl, + sizeof(ittl)); + + if (res == -1) { + DEBUG_PRINTF("setsockopt IPV6_MULTICAST_HOPS failed"); + + return (-1); + } + } else { + res = setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ittl, + sizeof(ittl)); + + if (res == -1) { + DEBUG_PRINTF("setsockopt IPV6_UNICAST_HOPS failed"); + + return (-1); + } + } + break; + default: + DEBUG_PRINTF("Unknown sockaddr family"); + errx(1, "Unknown sockaddr family"); + } + + return (0); +} diff --git a/sockfunc.h b/sockfunc.h new file mode 100644 index 0000000..1bdc2e6 --- /dev/null +++ b/sockfunc.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#ifndef _SOCKFUNC_H_ +#define _SOCKFUNC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +enum sf_transport_method { + SF_TM_ASM, + SF_TM_SSM, + SF_TM_IPBC, +}; + +enum sf_cast_type { + SF_CT_UNI, + SF_CT_MULTI, + SF_CT_BROAD, +}; + +extern int sf_bind_socket(const struct sockaddr *bind_addr, int sock); + +extern const char *sf_cast_type_to_str(enum sf_cast_type cast_type); + +extern int sf_create_multicast_socket(const struct sockaddr *mcast_addr, + const struct sockaddr *local_addr, const char *local_ifname, uint8_t ttl, + int allow_mcast_loop, enum sf_transport_method transport_method, + const struct ai_list *remote_addrs, int receive_timestamp, int force_recvttl, int sndbuf_size, + int rcvbuf_size, uint16_t bind_port); + +extern int sf_create_udp_socket(const struct sockaddr *sa); + +extern int sf_create_unicast_socket(const struct sockaddr *local_addr, uint8_t ttl, + int mcast_send, int allow_mcast_loop, const char *local_ifname, + enum sf_transport_method transport_method, int receive_timestamp, int force_recvttl, + int sndbuf_size, int rcvbuf_size, uint16_t *bind_port); + +extern int sf_is_ipbc_supported(void); + +extern int sf_is_ssm_supported(void); + +extern int sf_mcast_join_asm_group(const struct sockaddr *mcast_addr, + const struct sockaddr *local_addr, const char *local_ifname, int sock); + +extern int sf_mcast_join_ssm_group(const struct sockaddr *mcast_addr, + const struct sockaddr *local_addr, const struct sockaddr *remote_addr, + const char *local_ifname, int sock); + +extern int sf_mcast_join_ssm_group_list(const struct sockaddr *mcast_addr, + const struct sockaddr *local_addr, const struct ai_list *remote_addrs, + const char *local_ifname, int sock); + +extern int sf_set_socket_buf_size(int sock, int snd_buf, int buf_size, int *new_buf_size, + int force_buf_size); + +extern int sf_set_socket_broadcast(int sock, int enable); +extern int sf_set_socket_ipv6only(const struct sockaddr *sa, int sock); +extern int sf_set_socket_mcast_if(const struct sockaddr *local_addr, int sock, + const char *local_ifname); + +extern int sf_set_socket_mcast_loop(const struct sockaddr *mcast_addr, int sock, int enable); +extern int sf_set_socket_recvttl(const struct sockaddr *sa, int sock); +extern int sf_set_socket_reuse(int sock); +extern int sf_set_socket_timestamp(int sock); +extern int sf_set_socket_ttl(const struct sockaddr *sa, enum sf_cast_type cast_type, int sock, + uint8_t ttl); + +#ifdef __cplusplus +} +#endif + +#endif /* _SOCKFUNC_H_ */ diff --git a/tlv.c b/tlv.c new file mode 100644 index 0000000..0b35ff4 --- /dev/null +++ b/tlv.c @@ -0,0 +1,565 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#include + +#include + +#ifdef __sun +#include +#endif /* __sun */ + +#include +#define __STDC_FORMAT_MACROS +#include +#include +#include +#include + +#include "addrfunc.h" +#include "logging.h" +#include "omping.h" +#include "tlv.h" +#include "util.h" + +static int tlv_add_actual_ts(char *msg, size_t msg_len, size_t *pos, enum tlv_opt_type opt); + +static int tlv_add_sas(char *msg, size_t msg_len, size_t *pos, enum tlv_opt_type opt, + const struct sockaddr_storage *sas, int store_prefix_len); + +static int tlv_add_ts(char *msg, size_t msg_len, size_t *pos, enum tlv_opt_type opt, + struct timeval *tv); + +static int tlv_add_u8(char *msg, size_t msg_len, size_t *pos, enum tlv_opt_type opt, + uint8_t val); + +/* + * Add option opt_type with length opt_len and value to message msg with msg_len length to position + * pos. Position is automatically adjusted to new position, so subsequent calls of function add + * new option to correct position. Function returns 0 on success, otherwise -1. + */ +int +tlv_add(char *msg, size_t msg_len, size_t *pos, enum tlv_opt_type opt_type, uint16_t opt_len, + const void *value) +{ + uint16_t nlen; + uint16_t nopt_type; + + DEBUG2_PRINTF("Add option %"PRIu16" with len %"PRIu16" pos %zu", opt_type, opt_len, *pos); + + if (*pos + sizeof(nopt_type) + sizeof(nlen) + opt_len > msg_len) { + DEBUG2_PRINTF("Can't store option. msg_len too small."); + return (-1); + } + + nopt_type = ntohs((uint16_t)opt_type); + memcpy(msg + *pos, &nopt_type, sizeof(nopt_type)); + *pos += sizeof(nopt_type); + + nlen = htons(opt_len); + + memcpy(msg + *pos, &nlen, sizeof(nlen)); + *pos += sizeof(nlen); + + memcpy(msg + *pos, value, opt_len); + + *pos += opt_len; + + return (0); +} + +/* + * Add TLV with actual time stamp + */ +static int +tlv_add_actual_ts(char *msg, size_t msg_len, size_t *pos, enum tlv_opt_type opt) +{ + struct timeval tv; + + tv = util_get_time(); + + return (tlv_add_ts(msg, msg_len, pos, opt, &tv)); +} + +/* + * Add TLV with actual client time stamp + */ +int +tlv_add_client_tstamp(char *msg, size_t msg_len, size_t *pos) +{ + return (tlv_add_actual_ts(msg, msg_len, pos, TLV_OPT_TYPE_CLIENT_TSTAMP)); +} + +/* + * Add TLV with mcast group + */ +int +tlv_add_mcast_grp(char *msg, size_t msg_len, size_t *pos, const struct sockaddr_storage *sas) +{ + return (tlv_add_sas(msg, msg_len, pos, TLV_OPT_TYPE_MCAST_GRP, sas, 0)); +} + +/* + * Add TLV with mcast prefix + */ +int +tlv_add_mcast_prefix(char *msg, size_t msg_len, size_t *pos, const struct sockaddr_storage *sas) +{ + return (tlv_add_sas(msg, msg_len, pos, TLV_OPT_TYPE_MCAST_PREFIX, sas, 1)); +} + +/* + * Add TLV with option request option. Options are passed in opts array with opts_len length. + */ +int +tlv_add_opt_request(char *msg, size_t msg_len, size_t *pos, uint16_t *opts, size_t opts_len) +{ + char *value; + size_t val_len; + unsigned int i; + uint16_t opt; + + if (opts_len == 0) + return (-1); + + val_len = opts_len * sizeof(uint16_t); + + value = (char *)alloca(val_len); + + for (i = 0; i < opts_len; i++) { + opt = htons(opts[i]); + + memcpy(value + i * sizeof(opt), &opt, sizeof(opt)); + } + + return (tlv_add(msg, msg_len, pos, TLV_OPT_TYPE_OPT_REQUEST, val_len, value)); +} + +/* + * Add TLV with sockaddr_storage ip address. If store_prefix_len is set, prefix length of address + * (always full prefix) is also stored. + */ +static int +tlv_add_sas(char *msg, size_t msg_len, size_t *pos, enum tlv_opt_type opt, + const struct sockaddr_storage *sas, int store_prefix_len) +{ + char *value; + void *addr_pointer; + size_t addr_len; + size_t opt_len; + uint16_t af; + uint8_t pref_len_val; + + switch (sas->ss_family) { + case AF_INET: + af = AF_IANA_IP; + addr_len = sizeof(struct in_addr); + addr_pointer = &((struct sockaddr_in *)sas)->sin_addr; + break; + case AF_INET6: + af = AF_IANA_IP6; + addr_len = sizeof(struct in6_addr); + addr_pointer = &((struct sockaddr_in6 *)sas)->sin6_addr; + break; + default: + DEBUG_PRINTF("Unknown sas family %d", sas->ss_family); + errx(1, "Unknown sas family %d", sas->ss_family); + } + + pref_len_val = addr_len * 8; + + opt_len = sizeof(af) + addr_len; + + if (store_prefix_len) + opt_len += sizeof(pref_len_val); + + value = (char *)alloca(opt_len); + + af = htons(af); + + memcpy(value, &af, sizeof(af)); + if (store_prefix_len) + memcpy(value + sizeof(af), &pref_len_val, sizeof(pref_len_val)); + + memcpy(value + sizeof(af) + (store_prefix_len ? sizeof(pref_len_val) : 0), addr_pointer, + addr_len); + + return (tlv_add(msg, msg_len, pos, opt, opt_len, value)); + +} + +/* + * Add sequence number TLV. + */ +int +tlv_add_seq_num(char *msg, size_t msg_len, size_t *pos, uint32_t seq) +{ + uint32_t nseq; + + nseq = htonl(seq); + return (tlv_add(msg, msg_len, pos, TLV_OPT_TYPE_SEQ_NUM, sizeof(nseq), &nseq)); +} + +/* + * Add TLV with server info + */ +int +tlv_add_server_info(char *msg, size_t msg_len, size_t *pos, const char *server_info) +{ + if (strlen(server_info) == 0) + return (-1); + + return (tlv_add(msg, msg_len, pos, TLV_OPT_TYPE_SERVER_INFO, strlen(server_info), + server_info)); +} + +/* + * Add TLV with actual server timestamp + */ +int +tlv_add_server_tstamp(char *msg, size_t msg_len, size_t *pos) +{ + return (tlv_add_actual_ts(msg, msg_len, pos, TLV_OPT_TYPE_SERVER_TSTAMP)); +} + +/* + * Add timestamp + */ +static int +tlv_add_ts(char *msg, size_t msg_len, size_t *pos, enum tlv_opt_type opt, struct timeval *tv) +{ + char value[8]; + uint32_t u32; + + u32 = tv->tv_sec; + u32 = htonl(u32); + memcpy(value, &u32, sizeof(u32)); + + u32 = tv->tv_usec; + u32 = htonl(u32); + memcpy(value + sizeof(u32), &u32, sizeof(u32)); + + return (tlv_add(msg, msg_len, pos, opt, sizeof(value), value)); +} + +/* + * Add server's TTL TLV + */ +int +tlv_add_ttl(char *msg, size_t msg_len, size_t *pos, uint8_t ttl) +{ + return (tlv_add_u8(msg, msg_len, pos, TLV_OPT_TYPE_TTL, ttl)); +} + +/* + * Add uint8_t type as option opt. + */ +static int +tlv_add_u8(char *msg, size_t msg_len, size_t *pos, enum tlv_opt_type opt, uint8_t val) +{ + return (tlv_add(msg, msg_len, pos, opt, sizeof(val), &val)); +} + +/* + * Add TLV with protocol version. + */ +int +tlv_add_version(char *msg, size_t msg_len, size_t *pos) +{ + uint8_t ver; + + ver = PROTOCOL_VERSION; + + return (tlv_add_u8(msg, msg_len, pos, TLV_OPT_TYPE_VERSION, ver)); +} + +/* + * Return pointer to tlv data + */ +const char * +tlv_iter_get_data(const struct tlv_iterator *tlv_iter) +{ + return (tlv_iter->msg + tlv_iter->pos + 2 * sizeof(uint16_t)); +} + +/* + * Get length of item currently pointed by iterator + */ +uint16_t +tlv_iter_get_len(const struct tlv_iterator *tlv_iter) +{ + uint16_t len; + + memcpy(&len, tlv_iter->msg + tlv_iter->pos + sizeof(uint16_t), sizeof(len)); + len = ntohs(len); + + return (len); +} + +/* + * Get type of item currently pointed by iterator + */ +enum tlv_opt_type +tlv_iter_get_type(const struct tlv_iterator *tlv_iter) +{ + uint16_t res; + + memcpy(&res, tlv_iter->msg + tlv_iter->pos, sizeof(res)); + res = ntohs(res); + + return ((enum tlv_opt_type)res); +} + +/* + * Initialize iterator + */ +void +tlv_iter_init(const char *msg, size_t msg_len, struct tlv_iterator *tlv_iter) +{ + + tlv_iter->msg = msg; + tlv_iter->msg_len = msg_len; + tlv_iter->pos = 0; +} + +/* + * Copy item from message pointed with iterator tlv_iter to new message new_msg with new_msg_len + * length to position pos. Return 0 on success, and -1 on failure. + */ +int +tlv_iter_item_copy(const struct tlv_iterator *tlv_iter, char *new_msg, size_t new_msg_len, + size_t *pos) +{ + size_t item_size; + + DEBUG2_PRINTF("Copy option %"PRIu16" with len %"PRIu16" pos %zu", + tlv_iter_get_type(tlv_iter), tlv_iter_get_len(tlv_iter), *pos); + + item_size = tlv_iter_get_len(tlv_iter) + 2 * sizeof(uint16_t); + + if (*pos + item_size > new_msg_len) { + DEBUG2_PRINTF("Can't copy option. new_msg_len too small."); + + return (-1); + } + + memcpy(new_msg + *pos, tlv_iter->msg + tlv_iter->pos, item_size); + + *pos += item_size; + + return (0); +} + +/* + * Move iterator to the next item. Returns 0 when move was successful, or -1 if end of the message + * was reached. + */ +int +tlv_iter_next(struct tlv_iterator *tlv_iter) +{ + uint16_t nlen; + + if (tlv_iter->pos == 0) { + tlv_iter->pos = 1; + return (0); + } + + nlen = tlv_iter_get_len(tlv_iter); + + if (tlv_iter->pos + sizeof(uint16_t) + sizeof(nlen) + nlen >= tlv_iter->msg_len) { + return (-1); + } + + tlv_iter->pos += sizeof(uint16_t) + sizeof(nlen) + nlen; + + return (0); +} + +/* + * Compare msg item pointed by iterator of MCAST_PREFIX type with sockaddr address + */ +int +tlv_iter_pref_eq(const struct tlv_iterator *tlv_iter, const struct sockaddr_storage *sas) +{ + uint16_t tlv_len; + uint16_t u16; + uint8_t pref_len; + uint8_t min_len; + + if (tlv_iter_get_type(tlv_iter) != TLV_OPT_TYPE_MCAST_PREFIX) { + return (0); + } + + tlv_len = tlv_iter_get_len(tlv_iter); + + if (tlv_len <= 2) { + return (0); + } + + memcpy(&u16, tlv_iter_get_data(tlv_iter), sizeof(u16)); + u16 = ntohs(u16); + + if (u16 != AF_IANA_IP && u16 != AF_IANA_IP6) { + return (0); + } + + memcpy(&pref_len, tlv_iter_get_data(tlv_iter) + 2, sizeof(pref_len)); + + min_len = pref_len / 8; + if (pref_len % 8 != 0) + min_len++; + + if (tlv_len - 3 < min_len) { + return (0); + } + + return (tlv_pref_eq(sas, u16, pref_len, tlv_iter_get_data(tlv_iter) + 3)); +} + +/* + * Compare sockaddr_storage address sas with mcast_grp received in message with length + * mcast_grp_len. Return 0 if addresses mismatch, otherwise not 0. + */ +int +tlv_mcast_grp_eq(const struct sockaddr_storage *sas, const char *mcast_grp, size_t mcast_grp_len) +{ + uint16_t u16; + + memcpy(&u16, mcast_grp, sizeof(u16)); + u16 = ntohs(u16); + + if (!((u16 == AF_IANA_IP && mcast_grp_len == 6) || + (u16 == AF_IANA_IP6 && mcast_grp_len == 18))) { + return (0); + } + + if (u16 == AF_IANA_IP && sas->ss_family != AF_INET) { + return (0); + } + + if (u16 == AF_IANA_IP6 && sas->ss_family != AF_INET6) { + return (0); + } + + return (tlv_pref_eq(sas, u16, (mcast_grp_len - 2) * 8, mcast_grp + 2)); +} + +/* + * Return static string with opt name + */ +const char * +tlv_opt_type_to_str(enum tlv_opt_type opt) +{ + const char *res; + + switch (opt) { + case TLV_OPT_TYPE_VERSION: res = "Version"; break; + case TLV_OPT_TYPE_CLIENT_ID: res = "Client ID"; break; + case TLV_OPT_TYPE_SEQ_NUM: res = "Sequence Number"; break; + case TLV_OPT_TYPE_CLIENT_TSTAMP: res = "Client Timestamp"; break; + case TLV_OPT_TYPE_MCAST_GRP: res = "Multicast Group"; break; + case TLV_OPT_TYPE_OPT_REQUEST: res = "Option Request Option"; break; + case TLV_OPT_TYPE_SERVER_INFO: res = "Server Information"; break; + case TLV_OPT_TYPE_TTL: res = "TTL"; break; + case TLV_OPT_TYPE_MCAST_PREFIX: res = "Multicast Prefix"; break; + case TLV_OPT_TYPE_SES_ID: res = "Session ID"; break; + case TLV_OPT_TYPE_SERVER_TSTAMP: res = "Server Timestamp"; break; + default: res = "Unknown"; break; + } + + return (res); +} + +/* + * Compare prefix address with sockaddr_storage address. iana_af is IANA address family, prefix is + * prefix length and addr is pointer to bytes of prefixed address. Only needed number of bytes is + * compared. + */ +int +tlv_pref_eq(const struct sockaddr_storage *sas, uint16_t iana_af, uint8_t prefix, const char *addr) +{ + char sas_addr[32]; + size_t sas_addr_len; + uint16_t sas_iana_af; + unsigned char cb1, cb2; + uint8_t plen_max, plen_min; + + memset(sas_addr, 0, sizeof(sas_addr)); + + switch (sas->ss_family) { + case AF_INET: + sas_iana_af = AF_IANA_IP; + + plen_min = 4; + plen_max = 32; + + sas_addr_len = sizeof(struct in_addr); + memcpy(sas_addr, &((struct sockaddr_in *)sas)->sin_addr, sas_addr_len); + break; + case AF_INET6: + sas_iana_af = AF_IANA_IP6; + + plen_min = 8; + plen_max = 128; + + sas_addr_len = sizeof(struct in6_addr); + memcpy(sas_addr, &((struct sockaddr_in6 *)sas)->sin6_addr, sas_addr_len); + break; + default: + DEBUG_PRINTF("Unknown ss family %d", sas->ss_family); + errx(1, "Unknown ss family %d", sas->ss_family); + } + + if (iana_af != sas_iana_af) { + return (0); + } + + if (prefix == 0) { + /* + * Wildcard + */ + return (1); + } + + if (prefix < plen_min || prefix > plen_max) { + return (0); + } + + /* + * Full bytes comparation + */ + if (memcmp(sas_addr, addr, prefix / 8) != 0) { + return (0); + } + + + /* + * Rest bit comparation + */ + if (prefix % 8 != 0 && prefix / 8 < sizeof(sas_addr_len)) { + cb1 = (unsigned char)(sas_addr[prefix / 8] & (0xff << (8 - (prefix % 8)))); + cb2 = (unsigned char)(addr[prefix / 8] & (0xff << (8 - (prefix % 8)))); + if (cb1 != cb2) { + return (0); + } + } + + return (1); +} diff --git a/tlv.h b/tlv.h new file mode 100644 index 0000000..26f1afc --- /dev/null +++ b/tlv.h @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#ifndef _TLV_H_ +#define _TLV_H_ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Definitions + */ + +/* + * Address families how defined by AIANA + */ +enum { + AF_IANA_IP = 1, + AF_IANA_IP6 = 2, +}; + +/* + * TLV option type definition + */ +enum tlv_opt_type { + TLV_OPT_TYPE_VERSION = 0, + TLV_OPT_TYPE_CLIENT_ID = 1, + TLV_OPT_TYPE_SEQ_NUM = 2, + TLV_OPT_TYPE_CLIENT_TSTAMP = 3, + TLV_OPT_TYPE_MCAST_GRP = 4, + TLV_OPT_TYPE_OPT_REQUEST = 5, + TLV_OPT_TYPE_SERVER_INFO = 6, + /* 7 and 8 are reserved and copied only */ + TLV_OPT_TYPE_TTL = 9, + TLV_OPT_TYPE_MCAST_PREFIX = 10, + TLV_OPT_TYPE_SES_ID = 11, + TLV_OPT_TYPE_SERVER_TSTAMP = 12, +}; + +/* + * tlv_iterator type + */ +struct tlv_iterator { + const char *msg; + size_t msg_len; + size_t pos; +}; + +/* + * Functions + */ +extern int tlv_add(char *msg, size_t msg_len, size_t *pos, enum tlv_opt_type opt_type, + uint16_t opt_len, const void *value); + +extern int tlv_add_client_tstamp(char *msg, size_t msg_len, size_t *pos); + +extern int tlv_add_mcast_grp(char *msg, size_t msg_len, size_t *pos, + const struct sockaddr_storage *sas); + +extern int tlv_add_mcast_prefix(char *msg, size_t msg_len, size_t *pos, + const struct sockaddr_storage *sas); + +extern int tlv_add_opt_request(char *msg, size_t msg_len, size_t *pos, uint16_t *opts, + size_t opts_len); + +extern int tlv_add_seq_num(char *msg, size_t msg_len, size_t *pos, uint32_t seq); + +extern int tlv_add_server_info(char *msg, size_t msg_len, size_t *pos, + const char *server_info); + +extern int tlv_add_server_tstamp(char *msg, size_t msg_len, size_t *pos); + +extern int tlv_add_ttl(char *msg, size_t msg_len, size_t *pos, uint8_t ttl); + +extern int tlv_add_version(char *msg, size_t msg_len, size_t *pos); + +extern const char *tlv_iter_get_data(const struct tlv_iterator *tlv_iter); + +extern uint16_t tlv_iter_get_len(const struct tlv_iterator *tlv_iter); + +extern enum tlv_opt_type tlv_iter_get_type(const struct tlv_iterator *tlv_iter); + +extern void tlv_iter_init(const char *msg, size_t msg_len, struct tlv_iterator *tlv_iter); + +extern int tlv_iter_item_copy(const struct tlv_iterator *tlv_iter, char *new_msg, + size_t new_msg_len, size_t *pos); + +extern int tlv_iter_next(struct tlv_iterator *tlv_iter); + +extern int tlv_iter_pref_eq(const struct tlv_iterator *tlv_iter, + const struct sockaddr_storage *sas); + +extern int tlv_mcast_grp_eq(const struct sockaddr_storage *sas, const char *mcast_grp, + size_t mcast_grp_len); + +extern const char *tlv_opt_type_to_str(enum tlv_opt_type opt); + +extern int tlv_pref_eq(const struct sockaddr_storage *sas, uint16_t iana_af, uint8_t prefix, + const char *addr); + +#ifdef __cplusplus +} +#endif + +#endif /* _TLV_H_ */ diff --git a/util.c b/util.c new file mode 100644 index 0000000..14fd730 --- /dev/null +++ b/util.c @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef __CYGWIN__ +#include +#endif + +#include "logging.h" +#include "util.h" + +/* + * Function prototypes + */ +#ifdef __CYGWIN__ +static int util_cygwin_gettimeofday(struct timeval *tv, struct timezone *tz); +#endif + +static void util_gen_id(char *id, size_t len, const struct ai_item *ai_item, + const struct sockaddr_storage *sas); + +static void util_gen_id_add_sas(char *id, size_t len, size_t *pos, + const struct sockaddr_storage *sas); + +/* + * Functions implementation + */ + +#ifdef __CYGWIN__ +/* + * cygwin version of gettimeofday but with microseconds precision. Uses windows Performance + * Counters to achieve precision if possible, otherwise cygwin gettimeofday implementation + * is used. + * Return 0 on success, otherwise -1. + */ +int +util_cygwin_gettimeofday(struct timeval *tv, struct timezone *tz) +{ + /* Frequency of performance counter */ + static LARGE_INTEGER freq; + /* Offset of starting pc */ + static LARGE_INTEGER perf_count_offset; + /* Actual pc */ + static LARGE_INTEGER perf_count; + /* Microsenconds base time */ + static uint64_t us_base = 0; + /* Function was not called yet */ + static int initialized = 0; + /* If not used pf, fallback to gettimeofday implementation */ + static BOOL use_pf = 0; + /* Tmp timeval */ + struct timeval tv2; + /* Diff between offset pc and actual pc */ + int64_t perf_diff; + /* Actual time in microseconds */ + uint64_t us; + /* Time in microseconds returned by gettimeofday */ + uint64_t us_ref; + + if (!initialized) { + initialized = 1; + use_pf = QueryPerformanceFrequency(&freq); + if (use_pf) { + QueryPerformanceCounter(&perf_count_offset); + gettimeofday(&tv2, tz); + us_base = tv2.tv_sec * (uint64_t)1000000 + tv2.tv_usec; + } + } + + if (use_pf) { + QueryPerformanceCounter(&perf_count); + } else { + return (gettimeofday(tv, tz)); + } + + perf_diff = perf_count.QuadPart - perf_count_offset.QuadPart; + us = ((double)perf_diff / (double)freq.QuadPart) * 1000000.0 + us_base; + + gettimeofday(&tv2, tz); + us_ref = tv2.tv_sec * (uint64_t)1000000 + tv2.tv_usec; + + if (util_u64_absdiff(us, us_ref) > (uint64_t)1000000) { + us_base = us = us_ref; + perf_count_offset.QuadPart = perf_count.QuadPart; + } + + tv->tv_sec = us / (uint64_t)1000000; + tv->tv_usec = us % (uint64_t)1000000; + + return (0); +} +#endif /* __CYGWIN__ */ + +/* + * Returns absolute value of n + */ +double +util_fabs(double n) +{ + + return (n < 0 ? -n : n); +} + +/* + * generate random ID from current pid, random data from random(3) and optionally addresses ai_item + * and sas. ID is stored in id with maximum length len. + */ +static void +util_gen_id(char *id, size_t len, const struct ai_item *ai_item, + const struct sockaddr_storage *sas) +{ + pid_t pid; + size_t pos; + + /* + * First fill item with some random data + */ + for (pos = 0; pos < len; pos++) { +#if defined(__FreeBSD__) || defined(__OPENBSD__) + id[pos] = (unsigned char)arc4random_uniform(UCHAR_MAX); +#else + id[pos] = (unsigned char)random(); +#endif + } + + pos = 0; + + if (pos + sizeof(pid) < len) { + /* + * Add PID + */ + pid = getpid(); + memcpy(id, &pid, sizeof(pid)); + + pos += sizeof(pid); + } + + /* + * Add sas from ai_item + */ + if (ai_item != NULL) { + util_gen_id_add_sas(id, len, &pos, &ai_item->sas); + } + + if (sas != NULL) { + util_gen_id_add_sas(id, len, &pos, sas); + } +} + +/* + * Add IP address from sas to id with length len to position pos. Also adjust pos to position after + * added item. + */ +static void +util_gen_id_add_sas(char *id, size_t len, size_t *pos, const struct sockaddr_storage *sas) +{ + void *addr_pointer; + size_t addr_len; + + switch (sas->ss_family) { + case AF_INET: + addr_pointer = &(((struct sockaddr_in *)sas)->sin_addr.s_addr); + addr_len = sizeof(struct in_addr); + break; + case AF_INET6: + addr_pointer = &(((struct sockaddr_in6 *)sas)->sin6_addr.s6_addr); + addr_len = sizeof(struct in6_addr); + break; + default: + DEBUG_PRINTF("Unknown ss family %d", sas->ss_family); + errx(1, "Unknown ss family %d", sas->ss_family); + } + + if (*pos + addr_len < len) { + memcpy(id + *pos, addr_pointer, addr_len); + *pos += addr_len; + } +} + +/* + * Generate client id. Client id has length CLIENTID_LEN and takes only local address. + */ +void +util_gen_cid(char *client_id, const struct ai_item *local_addr) +{ + util_gen_id(client_id, CLIENTID_LEN, local_addr, NULL); + + DEBUG2_HEXDUMP("generated CID: ", client_id, CLIENTID_LEN); +} + +/* + * Generate session id. Session id has length SESSIONID_LEN and takes local and remote addresses. + */ +void +util_gen_sid(char *session_id) +{ + util_gen_id(session_id, SESSIONID_LEN, NULL, NULL); + + DEBUG2_HEXDUMP("generated SESID: ", session_id, SESSIONID_LEN); +} + +/* + * Return current time stamp saved in timeval structure. + */ +struct timeval +util_get_time(void) +{ + struct timeval tv; + +#ifdef __CYGWIN__ + util_cygwin_gettimeofday(&tv, NULL); +#else + gettimeofday(&tv, NULL); +#endif + + return (tv); +} + +/* + * Initialize random number generator. + */ +void +util_random_init(const struct sockaddr_storage *local_addr) +{ + unsigned int seed; + unsigned int i; + + seed = time(NULL) + getpid(); + + for (i = 0; i < af_sas_len(local_addr); i++) { + seed += ((uint8_t *)local_addr)[i]; + } + + srandom(seed); +} + +/* + * Returns abs(t1 - t2) in miliseconds. + */ +uint64_t +util_time_absdiff(struct timeval t1, struct timeval t2) +{ + uint64_t u64t1, u64t2, tmp; + + u64t1 = t1.tv_usec / 1000 + t1.tv_sec * 1000; + u64t2 = t2.tv_usec / 1000 + t2.tv_sec * 1000; + + if (u64t2 > u64t1) { + tmp = u64t1; + u64t1 = u64t2; + u64t2 = tmp; + } + + return (u64t1 - u64t2); +} + +/* + * Return abs value of (t2 - t1) in ms double precission. + */ +double +util_time_double_absdiff(struct timeval t1, struct timeval t2) +{ + return (util_time_double_absdiff_us(t1, t2) / 1000.0); +} + +/* + * Return abs value of (t2 - t1) in ns (nano seconds) double precission. + */ +double +util_time_double_absdiff_ns(struct timeval t1, struct timeval t2) +{ + return (util_time_double_absdiff_us(t1, t2) * 1000.0); +} + +/* + * Return abs value of (t2 - t1) in us (micro seconds) double precission. + */ +double +util_time_double_absdiff_us(struct timeval t1, struct timeval t2) +{ + double dt1, dt2, tmp; + + dt1 = t1.tv_usec + t1.tv_sec * UTIL_NSINMS; + dt2 = t2.tv_usec + t2.tv_sec * UTIL_NSINMS; + + if (dt2 > dt1) { + tmp = dt1; + dt1 = dt2; + dt2 = tmp; + } + + return (dt1 - dt2); +} + +/* + * Return standard deviation based on m2 value and number of items n. Value is rounded to 0.001. + */ +double +util_ov_std_dev(double m2, uint64_t n) +{ + return (util_u64sqrt((uint64_t)util_ov_variance(m2, n))); +} + +/* + * On-line algorithm for compute variance. + * Based on Donald E. Knuth (1998). The Art of Computer Programming, volume 2: p. 232. + * function updats mean and m2. x is new value and n is absolute number of all items. + */ +void +util_ov_update(double *mean, double *m2, double x, uint64_t n) +{ + double delta; + + delta = x - *mean; + *mean = *mean + delta / n; + *m2 = *m2 + delta * (x - *mean); +} + +/* + * Return variance based on m2 value and number of items n. + */ +double +util_ov_variance(double m2, uint64_t n) +{ + return ((n > 1) ? (m2 / (n - 1)) : 0.0); +} + +/* + * Return number of miliseconds from timeval structure + */ +uint64_t +util_tv_to_ms(struct timeval t1) +{ + uint64_t u64; + + u64 = t1.tv_usec / 1000 + t1.tv_sec * 1000; + + return (u64); +} + +/* + * Return absolute difference between two unsigned 64-bit integers + */ +uint64_t +util_u64_absdiff(uint64_t u1, uint64_t u2) +{ + uint64_t tmpu; + + if (u1 > u2) { + tmpu = u1; + u1 = u2; + u2 = tmpu; + } + + return (u2 - u1); +} + +/* + * Return sqrt of 64bit unsigned int n + */ +uint32_t +util_u64sqrt(uint64_t n) +{ + double x, x2; + + if (n == 0) { + return (0); + } + + x = n; + + while (util_fabs((x2 = (x + n / x) / 2) - x) >= 0.5) { + x = x2; + } + + return ((uint32_t)x2); +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..bc26722 --- /dev/null +++ b/util.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2010-2011, Red Hat, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND RED HAT, INC. DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RED HAT, INC. BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Author: Jan Friesse + */ + +#ifndef _UTIL_H_ +#define _UTIL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "addrfunc.h" + +/* + * Definitions + */ + +/* + * Number of nanoseconds in one milisecond + */ +#define UTIL_NSINMS 1000000.0 + +/* + * (4 bytes of pid) + (16 bytes of IPV6 addr) + 4 bytes of random data + */ +enum { CLIENTID_LEN = (4 + 16 + 4) }; + +/* + * (4 bytes of pid) + 12 bytes of random data + */ +enum { SESSIONID_LEN = (4 + 12) }; + +/* + * Functions + */ +extern double util_fabs(double n); +extern void util_gen_cid(char *client_id, const struct ai_item *local_addr); +extern void util_gen_sid(char *session_id); +extern struct timeval util_get_time(void); +extern void util_random_init(const struct sockaddr_storage *local_addr); +extern uint64_t util_time_absdiff(struct timeval t1, struct timeval t2); +extern double util_time_double_absdiff(struct timeval t1, struct timeval t2); +extern double util_time_double_absdiff_ns(struct timeval t1, struct timeval t2); +extern double util_time_double_absdiff_us(struct timeval t1, struct timeval t2); +extern double util_ov_std_dev(double m2, uint64_t n); +extern void util_ov_update(double *mean, double *m2, double x, uint64_t n); +extern double util_ov_variance(double m2, uint64_t n); +extern uint64_t util_tv_to_ms(struct timeval t1); +extern uint64_t util_u64_absdiff(uint64_t u1, uint64_t u2); +extern uint32_t util_u64sqrt(uint64_t n); + +#ifdef __cplusplus +} +#endif + +#endif /* _UTIL_H_ */