diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index c78bb9d..9183d99 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -41,6 +41,7 @@ jobs: intltool \ libtool \ make \ + pkg-config \ sed - name: autogen diff --git a/.gitignore b/.gitignore index f5cc778..531d70e 100644 --- a/.gitignore +++ b/.gitignore @@ -21,8 +21,13 @@ autom4te.cache/ src/.libs/ src/*.l* +src/*.gcda +src/*.gcno tests/.libs/ tests/test +test/gwtest +test/*.gcda +test/*.gcno *~ *.o diff --git a/GNUmakefile.am b/GNUmakefile.am index ca4fbda..2d11a02 100644 --- a/GNUmakefile.am +++ b/GNUmakefile.am @@ -3,6 +3,10 @@ SUBDIRS = \ config \ src +if ENABLE_TEST +SUBDIRS += test +endif + EXTRA_DIST = \ autogen.sh \ config/m4/.secret-world-domination-project diff --git a/configure.ac b/configure.ac index 944d4bd..a6fe69b 100644 --- a/configure.ac +++ b/configure.ac @@ -95,7 +95,7 @@ if test "${CONFIG_DEBUG}" = "yes"; then CFLAGS="${CFLAGS} -Werror -Wsign-compare -Wfloat-equal -Wformat-security -g -O1" AC_DEFINE(DEBUG, 1, [debugging]) else - CFLAGS="${CFLAGS} -O2" + CFLAGS="${CFLAGS} -g -O2" fi @@ -115,6 +115,43 @@ if test "${CONFIG_ERROR_LOG}" = "no"; then AC_DEFINE(DISABLE_ERROR_LOG, 1, [disable error logging]) fi +# +# Enable gcov +# +AC_MSG_CHECKING([whether to enable gcov]) +AC_ARG_ENABLE([gcov], + [AS_HELP_STRING([--enable-gcov], [Enable gcov in build time (for debug, default is no)])], + [:], + [enable_gcov=no]) +AC_MSG_RESULT([$enable_gcov]) +if test "$enable_gcov" = "yes"; then + CFLAGS="${CFLAGS} -coverage" +fi + +# +# Enable address-sanitizer +# +AC_MSG_CHECKING([whether to enable address-sanitizer]) +AC_ARG_ENABLE([address-sanitizer], + [AS_HELP_STRING([--enable-address-sanitizer], [Enable address sanitizer in build time (for debug, default is no)])], + [:], + [enable_address_sanitizer=no]) +AC_MSG_RESULT([$enable_address_sanitizer]) +if test "$enable_address_sanitizer" = "yes"; then + CFLAGS="${CFLAGS} -fsanitize=address" +fi + +# +# Enable unit test +# +AC_ARG_ENABLE([test], + [AS_HELP_STRING([--enable-test], [Enable unit test build (cmocka is required, default is no])], + [:], + [enable_test=no]) +AM_CONDITIONAL([ENABLE_TEST], [test "$enable_test" = "yes"]) +AS_CASE( + ["$enable_test"], + [yes], [PKG_CHECK_MODULES([CMOCKA], [cmocka])],[]) AC_CONFIG_FILES([ GNUmakefile @@ -122,6 +159,7 @@ AC_CONFIG_FILES([ config/GNUmakefile include/GNUmakefile src/GNUmakefile + test/GNUmakefile ]) AC_OUTPUT diff --git a/include/GNUmakefile.am b/include/GNUmakefile.am index 12d9b04..c4cb17a 100644 --- a/include/GNUmakefile.am +++ b/include/GNUmakefile.am @@ -1,5 +1,6 @@ nobase_include_HEADERS = \ libsocketcan.h \ + libsocketcangw.h \ can_netlink.h MAINTAINERCLEANFILES = \ diff --git a/include/libsocketcangw.h b/include/libsocketcangw.h new file mode 100755 index 0000000..f2a9692 --- /dev/null +++ b/include/libsocketcangw.h @@ -0,0 +1,65 @@ +/* + * libsocketcangw.h + * + * (C) 2025 Naoto Yamaguchi + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, but without + * any warranty; without even the implied warranty of merchantability or fitness + * for a particular purpose. see the gnu lesser general public license for more + * details. + * + * you should have received a copy of the gnu lesser general public license + * along with this library; if not, write to the free software foundation, inc., + * 59 temple place, suite 330, boston, ma 02111-1307 usa + */ + + #ifndef _socketcangw_netlink_h + #define _socketcangw_netlink_h + + /** + * @file + * @brief API overview + */ + #include + + #ifdef __cplusplus + extern "C" { + #endif + +#define SOCKETCAN_GW_RULE_ECHO (0x00000001U) +#define SOCKETCAN_GW_RULE_FILTER (0x00000002U) + +struct s_socketcan_gw_rule { + unsigned int src_ifindex; + unsigned int dst_ifindex; + + unsigned int options; + + unsigned int echo; + struct can_filter filter; + }; +typedef struct s_socketcan_gw_rule socketcan_gw_rule_t; + +struct s_socketcan_gw_rules { + size_t rule_num; + socketcan_gw_rule_t **rules; + // internal use + size_t array_num; + }; +typedef struct s_socketcan_gw_rules socketcan_gw_rules_t; + +int cangw_add_rule(socketcan_gw_rule_t *rule); +int cangw_delete_rule(socketcan_gw_rule_t *rule); +int cangw_clean_rule(void); +int cangw_get_rules(socketcan_gw_rules_t **gw_rules); +int cangw_release_rules(socketcan_gw_rules_t *gw_rules); + + #ifdef __cplusplus + } + #endif + + #endif \ No newline at end of file diff --git a/src/GNUmakefile.am b/src/GNUmakefile.am index 1709bbe..f3f8c8b 100644 --- a/src/GNUmakefile.am +++ b/src/GNUmakefile.am @@ -4,7 +4,10 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/include \ -I$(top_builddir)/include -libsocketcan_la_SOURCES = libsocketcan.c +libsocketcan_la_SOURCES = \ + libsocketcan-utils.c \ + libsocketcan.c \ + libcangw.c libsocketcan_la_LDFLAGS = \ -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) @@ -15,3 +18,5 @@ libsocketcan_la_LDFLAGS = \ # MAINTAINERCLEANFILES = \ GNUmakefile.in + +CLEANFILES = *.gcda *.gcno \ No newline at end of file diff --git a/src/libcangw.c b/src/libcangw.c new file mode 100644 index 0000000..d1f02bb --- /dev/null +++ b/src/libcangw.c @@ -0,0 +1,555 @@ +/* libcangw.c + * + * (C) 2025 Naoto Yamaguchi + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/** + * @file + * @brief Socket CAN gateway library + */ + +#include +#include + +#include "libsocketcan-utils.h" + +#include + +struct s_request_data { + struct nlmsghdr nh; + struct rtcanmsg rtcan; + char buf[1500]; +}; + +#define RTCAN_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtcanmsg)))) +#define RTCAN_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtcanmsg)) + +/** + * @ingroup intern + * send_cangw_set_request - send request to add gw rule into kernel + * @param req pointer to request data. + * + * @return 0 if success + * @return -1 if operation is failed + * @return -2 if linux does not support can gateway + * @return -3 if returned fail response + */ +static int send_cangw_set_request(struct s_request_data *req) +{ + int result = 0; + int sock_fd = -1; + ssize_t ret = -1; + struct nlmsghdr *nlh = NULL; + struct nlmsgerr *rte = NULL; + struct sockaddr_nl nladdr; + unsigned char rxbuf[8192]; + + // Open netlink socket interface + sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sock_fd < 0) { + result = -1; + goto do_return; + } + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = 0; + nladdr.nl_groups = 0; + + ret = sendto(sock_fd, req, req->nh.nlmsg_len, 0, (struct sockaddr*)&nladdr, sizeof(nladdr)); + if (ret < 0) { + result = -1; + goto do_return; + } + + memset(rxbuf, 0, sizeof(rxbuf)); + ret = recv(sock_fd, &rxbuf, sizeof(rxbuf), 0); + if (ret < 0) { + result = -1; + goto do_return; + } + + nlh = (struct nlmsghdr *)rxbuf; + if (nlh->nlmsg_type != NLMSG_ERROR) { + result = -2; + goto do_return; + } + + rte = (struct nlmsgerr *)NLMSG_DATA(nlh); + if (rte->error < 0) { + result = -3; + } + +do_return: + if (sock_fd >= 0) { + close(sock_fd); + } + + return result; +} + +/** + * @ingroup intern + * push_gw_rule - push gw rule into gw_rules + * @param gw_rules pointer to rules structure of the can gateway. + * @param rule pointer to new rule to push into gw_rules. + * + * @return 0 if success + * @return -1 if operation is failed + */ +static int push_gw_rule(socketcan_gw_rules_t *gw_rules, socketcan_gw_rule_t *rule) +{ + int result = 0; + + if (gw_rules->rules == NULL) { + // Create rules array + gw_rules->rule_num = 0; // Initial size + gw_rules->array_num = 2; // Initial size + gw_rules->rules = (socketcan_gw_rule_t**)malloc(sizeof(socketcan_gw_rule_t*) * gw_rules->array_num); + if (gw_rules->rules == NULL) { + result = -1; + goto do_return; + } + } + + if (!(gw_rules->rule_num < gw_rules->array_num)) { + // Extend array + socketcan_gw_rule_t **pnew_rules = NULL; + gw_rules->array_num = gw_rules->array_num * 2; + pnew_rules = (socketcan_gw_rule_t**)realloc(gw_rules->rules, (sizeof(socketcan_gw_rule_t*) * gw_rules->array_num)); + if (pnew_rules != NULL) { + gw_rules->rules = pnew_rules; + } else { + result = -1; + goto do_return; + } + } + + gw_rules->rules[gw_rules->rule_num] = rule; + gw_rules->rule_num = gw_rules->rule_num + 1; + +do_return: + return result; +} + +/** + * @ingroup intern + * free_gw_rules - free memory of gw_rules + * @param gw_rules pointer to rules structure of the can gateway. + * + * @return 0 if success + */ +static int free_gw_rules(socketcan_gw_rules_t *gw_rules) +{ + for(size_t i=0; i < gw_rules->rule_num; i++) { + free(gw_rules->rules[i]); + gw_rules->rules[i] = NULL; + } + + free(gw_rules->rules); + gw_rules->rules = NULL; + gw_rules->rule_num = 0; + gw_rules->array_num = 0; + + return 0; +} + +/** + * @ingroup intern + * parse_listing_data - parse routing data that get from kernel + * @param gw_rules rules pointer to rules structure of the can gateway to add rule element. + * @param rxbuf buffer of received data from kernel. + * @param len buffer length of received data from kernel. + * + * @return 1 if completed to get routing rule from kernel + * @return 0 if end of received data + * @return -1 if operation is failed + */ +static int parse_listing_data(socketcan_gw_rules_t *gw_rules, unsigned char *rxbuf, int len) +{ + socketcan_gw_rule_t *rule = NULL; + struct rtcanmsg *rtc = NULL; + struct rtattr *rta = NULL; + struct nlmsghdr *nlh = NULL; + int rtlen = 0; + int result = 0; + + nlh = (struct nlmsghdr*)rxbuf; + + while (1) { + if (!NLMSG_OK(nlh, len)){ + result = 0; + break; + } + + if (nlh->nlmsg_type == NLMSG_ERROR) { + result = -1; + break; + } + + if (nlh->nlmsg_type == NLMSG_DONE) { + result = 1; + break; + } + + rtc = (struct rtcanmsg *)NLMSG_DATA(nlh); + if (rtc->can_family != AF_CAN) { + result = -1; + break; + } + + if (rtc->gwtype != CGW_TYPE_CAN_CAN) { + result = -1; + break; + } + + rule = (socketcan_gw_rule_t*)malloc(sizeof(socketcan_gw_rule_t)); + if (rule == NULL) { + result = -1; + goto error_return; + } + memset(rule, 0 ,sizeof(socketcan_gw_rule_t)); + + rta = (struct rtattr *) RTCAN_RTA(rtc); + rtlen = RTCAN_PAYLOAD(nlh); + while (RTA_OK(rta, rtlen)) { + switch(rta->rta_type) { + case CGW_SRC_IF: + rule->src_ifindex = (*(unsigned int*)RTA_DATA(rta)); + break; + case CGW_DST_IF: + rule->dst_ifindex = (*(unsigned int*)RTA_DATA(rta)); + break; + default: + break; + } + rta = RTA_NEXT(rta, rtlen); + } + + rule->options = (SOCKETCAN_GW_RULE_ECHO | SOCKETCAN_GW_RULE_FILTER); + + if ((rtc->flags & CGW_FLAGS_CAN_ECHO) == CGW_FLAGS_CAN_ECHO) { + rule->echo = 1; + } else { + rule->echo = 0; + } + + rta = (struct rtattr *) RTCAN_RTA(rtc); + rtlen = RTCAN_PAYLOAD(nlh); + while(RTA_OK(rta, rtlen)) { + switch(rta->rta_type) { + case CGW_FILTER: + { + struct can_filter *filter = (struct can_filter *)RTA_DATA(rta); + if (filter->can_id & CAN_INV_FILTER) { + rule->filter.can_id = (filter->can_id & ~CAN_INV_FILTER); + } else { + rule->filter.can_id = filter->can_id; + } + rule->filter.can_mask = filter->can_mask; + break; + } + + default: + break; + } + rta = RTA_NEXT(rta, rtlen); + } + + push_gw_rule(gw_rules, rule); + + nlh = NLMSG_NEXT(nlh, len); + } + + return result; + +error_return: + (void) free(rule); + return result; +} + +/** + * @ingroup intern + * @brief init_req_data - initialize for req data + * + * @param req pointer to s_request_data structure that is initialized by this function. + * @param flags value of the nlmsg_flags + * @param type value of the nlmsg_type + * + * Set a netlink request data from rule to req. + */ +static void init_req_data(struct s_request_data *req, unsigned short flags, unsigned short type) +{ + // Setup common message + memset(req, 0, sizeof(struct s_request_data)); + + req->nh.nlmsg_flags = flags; + req->nh.nlmsg_type = type; + req->nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtcanmsg)); + req->nh.nlmsg_seq = 0; + + req->rtcan.can_family = AF_CAN; + req->rtcan.gwtype = CGW_TYPE_CAN_CAN; + req->rtcan.flags = 0; +} + +/** + * @ingroup intern + * @brief operate_rule_options - operate to options of gw rule + * + * @param req pointer to s_request_data structure that is wrote the options. + * @param rule pointer to source data of the gw configuration rule + * + * Set a netlink request data from rule to req. + */ +static void operate_rule_options(struct s_request_data *req, socketcan_gw_rule_t *rule) +{ + if ((rule->options & SOCKETCAN_GW_RULE_ECHO) == SOCKETCAN_GW_RULE_ECHO) { + if (rule->echo == 1) { + req->rtcan.flags |= CGW_FLAGS_CAN_ECHO; + } + } + + if ((rule->options & SOCKETCAN_GW_RULE_FILTER) == SOCKETCAN_GW_RULE_FILTER) { + addattr_l(&req->nh, sizeof(struct s_request_data), CGW_FILTER, &rule->filter, sizeof(struct can_filter)); + } +} + +/** + * @ingroup extern + * cangw_add_rule - add routing rule to can gateway + * @param rule rule data of the can gateway. + * + * @return 0 if success + * @return -1 if operation is failed + * @return -2 if linux does not support can gateway + * @return -3 if argument is invalid + */ +int cangw_add_rule(socketcan_gw_rule_t *rule) +{ + int result = 0; + int ret = -1; + struct s_request_data req; + + if (rule == NULL) { + result = -3; + goto do_return; + } + + // Setup common message + init_req_data(&req, (NLM_F_REQUEST | NLM_F_ACK), RTM_NEWROUTE); + + if ((rule->src_ifindex == 0) || (rule->dst_ifindex == 0)) { + // invalid ifindex + result = -3; + goto do_return; + } + addattr_l(&req.nh, sizeof(req), CGW_SRC_IF, &rule->src_ifindex, sizeof(rule->src_ifindex)); + addattr_l(&req.nh, sizeof(req), CGW_DST_IF, &rule->dst_ifindex, sizeof(rule->dst_ifindex)); + + // operate options + operate_rule_options(&req, rule); + + ret = send_cangw_set_request(&req); + if (ret < 0) { + result = -1; + goto do_return; + } + +do_return: + return result; +} + +/** + * @ingroup extern + * cangw_delete_rule - delete routing rule to can gateway + * @param rule rule data of the can gateway. + * + * @return 0 if success + * @return -1 if operation is failed + * @return -2 if linux does not support can gateway + * @return -3 if argument is invalid + */ +int cangw_delete_rule(socketcan_gw_rule_t *rule) +{ + int result = 0; + int ret = -1; + struct s_request_data req; + + if (rule == NULL) { + result = -3; + goto do_return; + } + + // Setup common message + init_req_data(&req, (NLM_F_REQUEST | NLM_F_ACK), RTM_DELROUTE); + + if ((rule->src_ifindex == 0) || (rule->dst_ifindex == 0)) { + // invalid ifindex + result = -3; + goto do_return; + } + addattr_l(&req.nh, sizeof(req), CGW_SRC_IF, &rule->src_ifindex, sizeof(rule->src_ifindex)); + addattr_l(&req.nh, sizeof(req), CGW_DST_IF, &rule->dst_ifindex, sizeof(rule->dst_ifindex)); + + // operate options + operate_rule_options(&req, rule); + + ret = send_cangw_set_request(&req); + if (ret < 0) { + result = -1; + goto do_return; + } + +do_return: + return result; +} + +/** + * @ingroup extern + * cangw_clean_rule - delete all routing rule to can gateway + * + * @return 0 if success + * @return -1 if operation is failed + * @return -2 if linux does not support can gateway + */ +int cangw_clean_rule(void) +{ + int result = 0; + int ret = -1; + unsigned int ifindex = 0; + struct s_request_data req; + + // Setup common message + init_req_data(&req, (NLM_F_REQUEST | NLM_F_ACK), RTM_DELROUTE); + + // If src and dst ifindex set to 0, the all rule are deleted. + addattr_l(&req.nh, sizeof(req), CGW_SRC_IF, &ifindex, sizeof(ifindex)); + addattr_l(&req.nh, sizeof(req), CGW_DST_IF, &ifindex, sizeof(ifindex)); + + ret = send_cangw_set_request(&req); + if (ret < 0) { + result = -1; + goto do_return; + } + +do_return: + return result; +} + +/** + * @ingroup extern + * cangw_get_rules - get can gateway routing rule + * @param gw_rules double pointer to rules structure of the can gateway to get existing rules. + * + * @return 0 if success + * @return -1 if operation is failed + * @return -2 if linux does not support can gateway + * @return -3 if argument is invalid + */ +int cangw_get_rules(socketcan_gw_rules_t **gw_rules) +{ + int result = 0; + int sock_fd = -1; + ssize_t ret = -1; + struct s_request_data req; + struct sockaddr_nl nladdr; + socketcan_gw_rules_t *pgw_rules = NULL; + + if (gw_rules == NULL) { + result = -3; + goto do_return; + } + + // Setup common message + init_req_data(&req, (NLM_F_REQUEST | NLM_F_DUMP), RTM_GETROUTE); + + // Open netlink socket interface + sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sock_fd < 0) { + result = -2; + goto do_return; + } + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = 0; + nladdr.nl_groups = 0; + + ret = sendto(sock_fd, &req, req.nh.nlmsg_len, 0, (struct sockaddr*)&nladdr, sizeof(nladdr)); + if (ret < 0) { + result = -1; + goto do_return; + } + + pgw_rules = malloc(sizeof(socketcan_gw_rules_t)); + if (pgw_rules == NULL) { + result = -1; + goto do_return; + } + + pgw_rules->rule_num = 0; + pgw_rules->array_num = 0; + pgw_rules->rules = NULL; + + while (1) { + unsigned char rxbuf[8192]; + memset(rxbuf, 0, sizeof(rxbuf)); + + ret = recv(sock_fd, &rxbuf, sizeof(rxbuf), 0); + if (ret < 0) { + result = -1; + goto do_return; + } + + /* leave on errors or NLMSG_DONE */ + if (parse_listing_data(pgw_rules, rxbuf, ret)) + break; + } + + (*gw_rules) = pgw_rules; + +do_return: + if (sock_fd >= 0) { + close(sock_fd); + } + + return result; +} +/** + * @ingroup extern + * cangw_release_rules - delete routing rule to can gateway + * @param gw_rules rules pointer to rules structure of the can gateway to free allocated memory. + * + * @return 0 if success + * @return -3 if argument is invalid + */ +int cangw_release_rules(socketcan_gw_rules_t *gw_rules) +{ + int result = 0; + + if (gw_rules == NULL) { + result = -3; + goto do_return; + } + + (void) free_gw_rules(gw_rules); + (void) free(gw_rules); + +do_return: + return result; +} \ No newline at end of file diff --git a/src/libsocketcan-utils.c b/src/libsocketcan-utils.c new file mode 100644 index 0000000..aa83ff6 --- /dev/null +++ b/src/libsocketcan-utils.c @@ -0,0 +1,68 @@ +/* libsocketcan-utils.c + * + * (C) 2009 Luotao Fu + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +/** + * @file + * @brief libsocketcan utilities + */ +#include "libsocketcan-utils.h" + +__attribute__((__visibility__("hidden"))) +int addattr32(struct nlmsghdr *n, size_t maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { + fprintf(stderr, + "addattr32: Error! max allowed bound %zu exceeded\n", + maxlen); + return -1; + } + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), &data, 4); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + + return 0; +} + +__attribute__((__visibility__("hidden"))) +int addattr_l(struct nlmsghdr *n, size_t maxlen, int type, + const void *data, int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { + fprintf(stderr, + "addattr_l ERROR: message exceeded bound of %zu\n", + maxlen); + return -1; + } + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + + return 0; +} \ No newline at end of file diff --git a/src/libsocketcan-utils.h b/src/libsocketcan-utils.h new file mode 100644 index 0000000..1709b64 --- /dev/null +++ b/src/libsocketcan-utils.h @@ -0,0 +1,51 @@ +/* libsocketcan-utils.h + * + * (C) 2009 Luotao Fu + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef LIBSOCKETCAN_UTILS_H +#define LIBSOCKETCAN_UTILS_H + +#ifdef HAVE_CONFIG_H +#include "libsocketcan_config.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/* Define DISABLE_ERROR_LOG to disable printing of error messages to stderr. */ +#ifdef DISABLE_ERROR_LOG +#define perror(x) while (0) { perror(x); } +#define fprintf(stream, format, args...) while (0) { fprintf(stream, format, ##args); } +#endif + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +int addattr32(struct nlmsghdr *n, size_t maxlen, int type, __u32 data); +int addattr_l(struct nlmsghdr *n, size_t maxlen, int type, const void *data, int alen); +#endif //#ifndef LIBSOCKETCAN_UTILS_H \ No newline at end of file diff --git a/src/libsocketcan.c b/src/libsocketcan.c index 50237ed..f961890 100644 --- a/src/libsocketcan.c +++ b/src/libsocketcan.c @@ -21,10 +21,7 @@ * @file * @brief library code */ - -#ifdef HAVE_CONFIG_H -#include "libsocketcan_config.h" -#endif +#include "libsocketcan-utils.h" #include #include @@ -40,18 +37,9 @@ #include -/* Define DISABLE_ERROR_LOG to disable printing of error messages to stderr. */ -#ifdef DISABLE_ERROR_LOG -#define perror(x) while (0) { perror(x); } -#define fprintf(stream, format, args...) while (0) { fprintf(stream, format, ##args); } -#endif - #define parse_rtattr_nested(tb, max, rta) \ (parse_rtattr((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta))) -#define NLMSG_TAIL(nmsg) \ - ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) - #define IFLA_CAN_MAX (__IFLA_CAN_MAX - 1) #define IF_UP 1 @@ -108,49 +96,6 @@ parse_rtattr(struct rtattr **tb, int max, struct rtattr *rta, int len) } } -static int addattr32(struct nlmsghdr *n, size_t maxlen, int type, __u32 data) -{ - int len = RTA_LENGTH(4); - struct rtattr *rta; - - if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) { - fprintf(stderr, - "addattr32: Error! max allowed bound %zu exceeded\n", - maxlen); - return -1; - } - - rta = NLMSG_TAIL(n); - rta->rta_type = type; - rta->rta_len = len; - memcpy(RTA_DATA(rta), &data, 4); - n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; - - return 0; -} - -static int addattr_l(struct nlmsghdr *n, size_t maxlen, int type, - const void *data, int alen) -{ - int len = RTA_LENGTH(alen); - struct rtattr *rta; - - if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { - fprintf(stderr, - "addattr_l ERROR: message exceeded bound of %zu\n", - maxlen); - return -1; - } - - rta = NLMSG_TAIL(n); - rta->rta_type = type; - rta->rta_len = len; - memcpy(RTA_DATA(rta), data, alen); - n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); - - return 0; -} - /** * @ingroup intern * @brief send_mod_request - send a linkinfo modification request diff --git a/test/GNUmakefile.am b/test/GNUmakefile.am new file mode 100644 index 0000000..c538d7e --- /dev/null +++ b/test/GNUmakefile.am @@ -0,0 +1,15 @@ +bin_PROGRAMS = gwtest + +gwtest_SOURCES = gwtest.c + +gwtest_LDADD = \ + @CMOCKA_LIBS@ + +gwtest_CFLAGS = \ + -g \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/include \ + @CMOCKA_CFLAGS@ \ + -D_GNU_SOURCE + +CLEANFILES = *.gcda *.gcno \ No newline at end of file diff --git a/test/clean-test.sh b/test/clean-test.sh new file mode 100755 index 0000000..2061358 --- /dev/null +++ b/test/clean-test.sh @@ -0,0 +1,5 @@ +#!/bin/sh +ip link del vxcan0 +ip link del vcan0 +ip link del vcan1 + diff --git a/test/gen-test-report.h b/test/gen-test-report.h new file mode 100755 index 0000000..7786ca2 --- /dev/null +++ b/test/gen-test-report.h @@ -0,0 +1,9 @@ +#!/bin/sh + +rm -Rf ./coverage +mkdir -p ./coverage +mkdir -p ./coverage/report + +lcov -c --rc lcov_branch_coverage=1 -d . -o ./coverage/testcoverage.info +genhtml --rc lcov_branch_coverage=1 -o ./coverage/report -f ./coverage/testcoverage.info --num-spaces 4 + diff --git a/test/gwtest.c b/test/gwtest.c new file mode 100755 index 0000000..f81b5b0 --- /dev/null +++ b/test/gwtest.c @@ -0,0 +1,436 @@ +#include "libsocketcan-utils.c" +#include "libcangw.c" +#include +#include +#include + +int print_gw_rules(socketcan_gw_rules_t *gw_rules) +{ + char src_ifname[IF_NAMESIZE]; + char dst_ifname[IF_NAMESIZE]; + + for(size_t i=0; i < gw_rules->rule_num; i++) { + socketcan_gw_rule_t *rule = gw_rules->rules[i]; + + fprintf(stdout, "cangw: -s %s -d %s -f %03X:%X echo=%d\n", + if_indextoname(rule->src_ifindex, src_ifname), + if_indextoname(rule->dst_ifindex, dst_ifname), + rule->filter.can_id, + rule->filter.can_mask, + rule->echo + ); + } + + return 0; +} +//------------------------------------------------------------------------------------------ +static int rule_setup(void **state) { + int ret = -1; + unsigned long ifindex_tmp = 0; + + ifindex_tmp = if_nametoindex("vcan0"); + if (ifindex_tmp == 0) { + fprintf(stderr,"Did not create vcan0 interface. That interface needs in test.\n"); + return -1; + } + + ifindex_tmp = if_nametoindex("vcan1"); + if (ifindex_tmp == 0) { + fprintf(stderr,"Did not create vcan1 interface. That interface needs in test.\n"); + return -1; + } + + ifindex_tmp = if_nametoindex("vxcan0"); + if (ifindex_tmp == 0) { + fprintf(stderr,"Did not create vxcan0 interface. That interface needs in test.\n"); + return -1; + } + + ret = cangw_clean_rule(); + if (ret < 0) { + fprintf(stderr,"Could not clean rules.\n"); + return -1; + } + + return 0; +} + +static int rule_teardown(void **state) { + int ret = -1; + + ret = cangw_clean_rule(); + if (ret < 0) { + fprintf(stderr,"Could not clean rules.\n"); + return -1; + } + + return 0; +} +//------------------------------------------------------------------------------------------ +static void add_rule_test_10(void **state) { + int ret = -1; + socketcan_gw_rule_t gw_rule; + socketcan_gw_rules_t *gw_rules = NULL; + + memset(&gw_rule, 0, sizeof(gw_rule)); + + gw_rule.src_ifindex = if_nametoindex("vcan0"); + gw_rule.dst_ifindex = if_nametoindex("vcan1"); + + gw_rule.options |= SOCKETCAN_GW_RULE_ECHO; + gw_rule.echo = 1; + + gw_rule.options |= SOCKETCAN_GW_RULE_FILTER; + gw_rule.filter.can_id = 0x3C0; + gw_rule.filter.can_mask = 0xff0; + + ret = cangw_add_rule(&gw_rule); + assert_true(ret == 0); + + ret = cangw_get_rules(&gw_rules); + assert_true(ret == 0); + assert_non_null(gw_rules); + assert_true(gw_rules->rule_num == 1); + assert_true(gw_rules->rules[0]->src_ifindex == if_nametoindex("vcan0")); + assert_true(gw_rules->rules[0]->dst_ifindex == if_nametoindex("vcan1")); + assert_true(gw_rules->rules[0]->options == (SOCKETCAN_GW_RULE_ECHO | SOCKETCAN_GW_RULE_FILTER)); + assert_true(gw_rules->rules[0]->echo == 1); + assert_true(gw_rules->rules[0]->filter.can_id == 0x3C0); + assert_true(gw_rules->rules[0]->filter.can_mask == 0xff0); + + cangw_release_rules(gw_rules); +} +//------------------------------------------------------------------------------------------ +static void add_rule_test_11(void **state) { + int ret = -1; + socketcan_gw_rule_t gw_rule; + socketcan_gw_rules_t *gw_rules = NULL; + + memset(&gw_rule, 0, sizeof(gw_rule)); + + gw_rule.src_ifindex = if_nametoindex("vcan0"); + gw_rule.dst_ifindex = if_nametoindex("vxcan0"); + + gw_rule.options |= SOCKETCAN_GW_RULE_ECHO; + gw_rule.echo = 0; + + gw_rule.options |= SOCKETCAN_GW_RULE_FILTER; + gw_rule.filter.can_id = 0x3C0; + gw_rule.filter.can_mask = 0xff0; + + ret = cangw_add_rule(&gw_rule); + assert_true(ret == 0); + + ret = cangw_get_rules(&gw_rules); + assert_true(ret == 0); + assert_non_null(gw_rules); + assert_true(gw_rules->rule_num == 1); + assert_true(gw_rules->rules[0]->src_ifindex == if_nametoindex("vcan0")); + assert_true(gw_rules->rules[0]->dst_ifindex == if_nametoindex("vxcan0")); + assert_true(gw_rules->rules[0]->options == (SOCKETCAN_GW_RULE_ECHO | SOCKETCAN_GW_RULE_FILTER)); + assert_true(gw_rules->rules[0]->echo == 0); + assert_true(gw_rules->rules[0]->filter.can_id == 0x3C0); + assert_true(gw_rules->rules[0]->filter.can_mask == 0xff0); + + cangw_release_rules(gw_rules); +} +//------------------------------------------------------------------------------------------ +static void add_rule_test_12(void **state) { + int ret = -1; + socketcan_gw_rule_t gw_rule; + socketcan_gw_rules_t *gw_rules = NULL; + + memset(&gw_rule, 0, sizeof(gw_rule)); + + gw_rule.src_ifindex = if_nametoindex("vxcan0"); + gw_rule.dst_ifindex = if_nametoindex("vcan0"); + + gw_rule.options |= SOCKETCAN_GW_RULE_ECHO; + gw_rule.echo = 0; + + gw_rule.options |= SOCKETCAN_GW_RULE_FILTER; + gw_rule.filter.can_id = 0x188; + gw_rule.filter.can_mask = 0xfff; + + ret = cangw_add_rule(&gw_rule); + assert_true(ret == 0); + + ret = cangw_get_rules(&gw_rules); + assert_true(ret == 0); + assert_non_null(gw_rules); + assert_true(gw_rules->rule_num == 1); + assert_true(gw_rules->rules[0]->src_ifindex == if_nametoindex("vxcan0")); + assert_true(gw_rules->rules[0]->dst_ifindex == if_nametoindex("vcan0")); + assert_true(gw_rules->rules[0]->options == (SOCKETCAN_GW_RULE_ECHO | SOCKETCAN_GW_RULE_FILTER)); + assert_true(gw_rules->rules[0]->echo == 0); + assert_true(gw_rules->rules[0]->filter.can_id == 0x188); + assert_true(gw_rules->rules[0]->filter.can_mask == 0xfff); + + cangw_release_rules(gw_rules); +} +//------------------------------------------------------------------------------------------ +static void add_rule_test_13(void **state) { + int ret = -1; + socketcan_gw_rule_t gw_rule[2]; + socketcan_gw_rules_t *gw_rules = NULL; + + memset(&gw_rule[0], 0, sizeof(gw_rule[0])); + memset(&gw_rule[1], 0, sizeof(gw_rule[1])); + + gw_rule[0].src_ifindex = if_nametoindex("vcan0"); + gw_rule[0].dst_ifindex = if_nametoindex("vcan1"); + gw_rule[0].options |= SOCKETCAN_GW_RULE_ECHO; + gw_rule[0].echo = 1; + gw_rule[0].options |= SOCKETCAN_GW_RULE_FILTER; + gw_rule[0].filter.can_id = 0x001; + gw_rule[0].filter.can_mask = 0x0ff; + + gw_rule[1].src_ifindex = if_nametoindex("vxcan0"); + gw_rule[1].dst_ifindex = if_nametoindex("vcan0"); + gw_rule[1].options |= SOCKETCAN_GW_RULE_FILTER; + gw_rule[1].filter.can_id = 0x00f; + gw_rule[1].filter.can_mask = 0xff0; + + ret = cangw_add_rule(&gw_rule[1]); + assert_true(ret == 0); + + ret = cangw_add_rule(&gw_rule[0]); + assert_true(ret == 0); + + ret = cangw_get_rules(&gw_rules); + assert_true(ret == 0); + assert_non_null(gw_rules); + assert_true(gw_rules->rule_num == 2); + assert_true(gw_rules->rules[0]->src_ifindex == if_nametoindex("vcan0")); + assert_true(gw_rules->rules[0]->dst_ifindex == if_nametoindex("vcan1")); + assert_true(gw_rules->rules[0]->options == (SOCKETCAN_GW_RULE_ECHO | SOCKETCAN_GW_RULE_FILTER)); + assert_true(gw_rules->rules[0]->echo == 1); + assert_true(gw_rules->rules[0]->filter.can_id == 0x001); + assert_true(gw_rules->rules[0]->filter.can_mask == 0x0ff); + assert_true(gw_rules->rules[1]->src_ifindex == if_nametoindex("vxcan0")); + assert_true(gw_rules->rules[1]->dst_ifindex == if_nametoindex("vcan0")); + assert_true(gw_rules->rules[1]->options == (SOCKETCAN_GW_RULE_ECHO | SOCKETCAN_GW_RULE_FILTER)); + assert_true(gw_rules->rules[1]->echo == 0); + assert_true(gw_rules->rules[1]->filter.can_id == 0x00f); + assert_true(gw_rules->rules[1]->filter.can_mask == 0xff0); + + cangw_release_rules(gw_rules); +} +//------------------------------------------------------------------------------------------ +static void add_rule_test_14(void **state) { + int ret = -1; + socketcan_gw_rule_t gw_rule; + socketcan_gw_rules_t *gw_rules = NULL; + + memset(&gw_rule, 0, sizeof(gw_rule)); + + gw_rule.src_ifindex = if_nametoindex("vcan0"); + gw_rule.dst_ifindex = if_nametoindex("vcan1"); + + gw_rule.options |= SOCKETCAN_GW_RULE_ECHO; + gw_rule.echo = 1; + + gw_rule.options |= SOCKETCAN_GW_RULE_FILTER; + + canid_t id = 1; + for(int i=0;i < 256;i++) { + gw_rule.filter.can_id = id; + gw_rule.filter.can_mask = 0xfff; + ret = cangw_add_rule(&gw_rule); + assert_true(ret == 0); + id++; + } + + ret = cangw_get_rules(&gw_rules); + assert_true(ret == 0); + assert_non_null(gw_rules); + assert_true(gw_rules->rule_num == 256); + for(int i=0;i < 256;i++) { + id--; + assert_true(gw_rules->rules[i]->src_ifindex == if_nametoindex("vcan0")); + assert_true(gw_rules->rules[i]->dst_ifindex == if_nametoindex("vcan1")); + assert_true(gw_rules->rules[i]->options == (SOCKETCAN_GW_RULE_ECHO | SOCKETCAN_GW_RULE_FILTER)); + assert_true(gw_rules->rules[i]->echo == 1); + assert_true(gw_rules->rules[i]->filter.can_id == id); + assert_true(gw_rules->rules[i]->filter.can_mask == 0xfff); + } + + cangw_release_rules(gw_rules); +} +//------------------------------------------------------------------------------------------ +static void delete_rule_test_20(void **state) { + int ret = -1; + socketcan_gw_rule_t gw_rule; + socketcan_gw_rules_t *gw_rules = NULL; + + memset(&gw_rule, 0, sizeof(gw_rule)); + + gw_rule.src_ifindex = if_nametoindex("vcan0"); + gw_rule.dst_ifindex = if_nametoindex("vcan1"); + + gw_rule.options |= SOCKETCAN_GW_RULE_ECHO; + gw_rule.echo = 1; + + gw_rule.options |= SOCKETCAN_GW_RULE_FILTER; + gw_rule.filter.can_id = 0x3C0; + gw_rule.filter.can_mask = 0xff0; + + ret = cangw_add_rule(&gw_rule); + assert_true(ret == 0); + + ret = cangw_delete_rule(&gw_rule); + assert_true(ret == 0); + + ret = cangw_get_rules(&gw_rules); + assert_true(ret == 0); + assert_non_null(gw_rules); + assert_true(gw_rules->rule_num == 0); + + cangw_release_rules(gw_rules); +} +//------------------------------------------------------------------------------------------ +static void delete_rule_test_21(void **state) { + int ret = -1; + socketcan_gw_rule_t gw_rule; + socketcan_gw_rules_t *gw_rules = NULL; + + memset(&gw_rule, 0, sizeof(gw_rule)); + + gw_rule.src_ifindex = if_nametoindex("vcan0"); + gw_rule.dst_ifindex = if_nametoindex("vxcan0"); + + gw_rule.options |= SOCKETCAN_GW_RULE_ECHO; + gw_rule.echo = 0; + + gw_rule.options |= SOCKETCAN_GW_RULE_FILTER; + gw_rule.filter.can_id = 0x123; + gw_rule.filter.can_mask = 0x000; + + ret = cangw_add_rule(&gw_rule); + assert_true(ret == 0); + + ret = cangw_delete_rule(&gw_rule); + assert_true(ret == 0); + + ret = cangw_get_rules(&gw_rules); + assert_true(ret == 0); + assert_non_null(gw_rules); + assert_true(gw_rules->rule_num == 0); + + cangw_release_rules(gw_rules); +} +//------------------------------------------------------------------------------------------ +static void delete_rule_test_22(void **state) { + int ret = -1; + socketcan_gw_rule_t gw_rule; + socketcan_gw_rules_t *gw_rules = NULL; + + memset(&gw_rule, 0, sizeof(gw_rule)); + + gw_rule.src_ifindex = if_nametoindex("vxcan0"); + gw_rule.dst_ifindex = if_nametoindex("vcan0"); + + gw_rule.options |= SOCKETCAN_GW_RULE_ECHO; + gw_rule.echo = 0; + + gw_rule.options |= SOCKETCAN_GW_RULE_FILTER; + gw_rule.filter.can_id = 0x188; + gw_rule.filter.can_mask = 0xfff; + + ret = cangw_add_rule(&gw_rule); + assert_true(ret == 0); + + ret = cangw_delete_rule(&gw_rule); + assert_true(ret == 0); + + ret = cangw_get_rules(&gw_rules); + assert_true(ret == 0); + assert_non_null(gw_rules); + assert_true(gw_rules->rule_num == 0); + + cangw_release_rules(gw_rules); +} +//------------------------------------------------------------------------------------------ +static struct option long_options[] = { + {"add-rule", no_argument, 0, 10}, + {"delete-rule", no_argument, 0, 20}, + {"clean-rule", no_argument, 0, 20}, + {0, 0, 0, 0}, +}; + +int main(int argc, char *argv[]) +{ + int ret = -1; + + ret = getopt_long(argc, argv, "", long_options, NULL); + switch (ret) { + case 10: + { + { + const struct CMUnitTest tests[] = { + cmocka_unit_test(add_rule_test_10), + }; + (void) cmocka_run_group_tests(tests, rule_setup, rule_teardown); + } + { + const struct CMUnitTest tests[] = { + cmocka_unit_test(add_rule_test_11), + }; + (void) cmocka_run_group_tests(tests, rule_setup, rule_teardown); + } + { + const struct CMUnitTest tests[] = { + cmocka_unit_test(add_rule_test_12), + }; + (void) cmocka_run_group_tests(tests, rule_setup, rule_teardown); + } + { + const struct CMUnitTest tests[] = { + cmocka_unit_test(add_rule_test_13), + }; + (void) cmocka_run_group_tests(tests, rule_setup, rule_teardown); + } + { + const struct CMUnitTest tests[] = { + cmocka_unit_test(add_rule_test_14), + }; + (void) cmocka_run_group_tests(tests, rule_setup, rule_teardown); + } + break; + } + case 20: + { + { + const struct CMUnitTest tests[] = { + cmocka_unit_test(delete_rule_test_20), + }; + (void) cmocka_run_group_tests(tests, rule_setup, rule_teardown); + } + { + const struct CMUnitTest tests[] = { + cmocka_unit_test(delete_rule_test_21), + }; + (void) cmocka_run_group_tests(tests, rule_setup, rule_teardown); + } + { + const struct CMUnitTest tests[] = { + cmocka_unit_test(delete_rule_test_22), + }; + (void) cmocka_run_group_tests(tests, rule_setup, rule_teardown); + } + break; + } + case 30: + { + + break; + } + default: + { + (void) fprintf(stderr, "This option is not support.\n"); + break; + } + } + + return 0; +} \ No newline at end of file diff --git a/test/run-test.sh b/test/run-test.sh new file mode 100755 index 0000000..064b2a6 --- /dev/null +++ b/test/run-test.sh @@ -0,0 +1,13 @@ +#!/bin/sh -e + +ip link add dev vcan0 type vcan +ip link add dev vcan1 type vcan +ip link add vxcan0 type vxcan peer name vxcan1 + +./test/gwtest --add-rule +./test/gwtest --delete-rule + +ip link del vxcan0 +ip link del vcan0 +ip link del vcan1 +