From 563114a47ae03c988ca0e66eddda33d485e35f6b Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 17 Jul 2008 17:20:10 +0200 Subject: add berkeley socket filtering high-level API This patch adds an abstraction level to berkeley sockets filter (BSF) for Netlink sockets available since Linux kernel 2.6.26. This provides an easy way to attach filters without knowing about BSF at all. Signed-off-by: Pablo Neira Ayuso --- configure.in | 2 +- include/internal.h | 64 ++++ .../libnetfilter_conntrack.h | 35 ++ src/conntrack/Makefile.am | 3 +- src/conntrack/api.c | 93 +++++ src/conntrack/bsf.c | 416 +++++++++++++++++++++ src/conntrack/filter.c | 39 ++ utils/Makefile.am | 6 +- utils/conntrack_filter.c | 83 ++++ 9 files changed, 738 insertions(+), 3 deletions(-) create mode 100644 src/conntrack/bsf.c create mode 100644 src/conntrack/filter.c create mode 100644 utils/conntrack_filter.c diff --git a/configure.in b/configure.in index fef5508..6568334 100644 --- a/configure.in +++ b/configure.in @@ -4,7 +4,7 @@ AC_INIT AC_CANONICAL_SYSTEM -AM_INIT_AUTOMAKE(libnetfilter_conntrack, 0.0.96) +AM_INIT_AUTOMAKE(libnetfilter_conntrack, 0.0.97) AC_PROG_CC AM_PROG_LIBTOOL diff --git a/include/internal.h b/include/internal.h index 6661dbe..33b0dcd 100644 --- a/include/internal.h +++ b/include/internal.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -31,15 +32,22 @@ #define IPPROTO_UDPLITE 136 #endif +#ifndef IPPROTO_DCCP +#define IPPROTO_DCCP 33 +#endif + struct nfct_handle; +struct nfct_filter; typedef void (*set_attr)(struct nf_conntrack *ct, const void *value); typedef const void *(*get_attr)(const struct nf_conntrack *ct); typedef void (*copy_attr)(struct nf_conntrack *d, const struct nf_conntrack *o); +typedef void (*filter_attr)(struct nfct_filter *filter, const void *value); extern set_attr set_attr_array[]; extern get_attr get_attr_array[]; extern copy_attr copy_attr_array[]; +extern filter_attr filter_attr_array[]; typedef int (*nfct_handler)(struct nfct_handle *cth, struct nlmsghdr *nlh, void *arg); @@ -165,6 +173,50 @@ struct nf_conntrack { u_int32_t set[2]; }; +struct nfct_filter { + /* + * As many other objects in this library, the attributes are + * private. This gives us the chance to modify the layout and + * object size. + * + * Another observation, although this object might seem too + * memory consuming, it is only needed to build the filter. Thus, + * once it is attached, you can release this object. + */ + + /* + * This the layer 4 protocol map for filtering. + */ + u_int32_t l4proto_map[IPPROTO_MAX/32]; + + struct { + /* + * No limitations in the protocol filtering. We use a map of + * 16 bits per protocol. As for now, DCCP has 10 states, TCP has + * 10 states, SCTP has 8 state. Therefore, 16 bits is enough. + */ +#define __FILTER_PROTO_MAX 16 + u_int16_t map; + } l4proto_state[IPPROTO_MAX]; + +#define __FILTER_ADDR_SRC 0 +#define __FILTER_ADDR_DST 1 + + /* + * FIXME: For IPv4 filtering, up to 256 IPs or masks by now. + * This limitation is related to the existing autogenerated BSF code + * and the fact that the maximum jump offset if 2^8 = 256. + */ + u_int32_t l3proto_elems[2]; + struct { +#define __FILTER_ADDR_MAX 256 + u_int32_t addr; + u_int32_t mask; + } l3proto[2][__FILTER_ADDR_MAX]; + + u_int32_t set[1]; +}; + struct nf_expect { struct nf_conntrack master; struct nf_conntrack expected; @@ -193,6 +245,16 @@ static inline void unset_bit(int nr, u_int32_t *addr) addr[nr >> 5] &= ~(1UL << (nr & 31)); } +static inline void set_bit_u16(int nr, u_int16_t *addr) +{ + addr[nr >> 4] |= (1UL << (nr & 15)); +} + +static inline void unset_bit_u16(int nr, u_int16_t *addr) +{ + addr[nr >> 4] &= ~(1UL << (nr & 15)); +} + static inline int test_bit(int nr, const u_int32_t *addr) { return ((1UL << (nr & 31)) & (addr[nr >> 5])) != 0; @@ -224,6 +286,8 @@ int __setobjopt(struct nf_conntrack *ct, unsigned int option); int __getobjopt(const struct nf_conntrack *ct, unsigned int option); int __compare(const struct nf_conntrack *ct1, const struct nf_conntrack *ct2, unsigned int flags); +int __setup_netlink_socket_filter(int fd, struct nfct_filter *filter); + typedef void (*set_exp_attr)(struct nf_expect *exp, const void *value); typedef const void *(*get_exp_attr)(const struct nf_expect *exp); diff --git a/include/libnetfilter_conntrack/libnetfilter_conntrack.h b/include/libnetfilter_conntrack/libnetfilter_conntrack.h index a043f91..328cf8b 100644 --- a/include/libnetfilter_conntrack/libnetfilter_conntrack.h +++ b/include/libnetfilter_conntrack/libnetfilter_conntrack.h @@ -324,6 +324,41 @@ extern void nfct_copy_attr(struct nf_conntrack *ct1, const struct nf_conntrack *ct2, const enum nf_conntrack_attr type); +/* filter */ + +struct nfct_filter; + +extern struct nfct_filter *nfct_filter_create(void); +extern void nfct_filter_destroy(struct nfct_filter *filter); + +struct nfct_filter_proto { + u_int16_t proto; + u_int16_t state; +}; +struct nfct_filter_ipv4 { + u_int32_t addr; + u_int32_t mask; +}; + +enum nfct_filter_attr { + NFCT_FILTER_L4PROTO = 0, /* u_int32_t */ + NFCT_FILTER_L4PROTO_STATE, /* struct nfct_filter_proto */ + NFCT_FILTER_SRC_IPV4, /* struct nfct_filter_ipv4 */ + NFCT_FILTER_DST_IPV4, /* struct nfct_filter_ipv4 */ + NFCT_FILTER_MAX +}; + +extern void nfct_filter_add_attr(struct nfct_filter *filter, + const enum nfct_filter_attr attr, + const void *value); + +extern void nfct_filter_add_attr_u32(struct nfct_filter *filter, + const enum nfct_filter_attr attr, + const u_int32_t value); + +extern int nfct_filter_attach(int fd, struct nfct_filter *filter); +extern int nfct_filter_detach(int fd); + /* low level API: netlink functions */ extern int nfct_build_conntrack(struct nfnl_subsys_handle *ssh, diff --git a/src/conntrack/Makefile.am b/src/conntrack/Makefile.am index 6440dd7..94009ea 100644 --- a/src/conntrack/Makefile.am +++ b/src/conntrack/Makefile.am @@ -12,4 +12,5 @@ libnfconntrack_la_SOURCES = api.c callback.c \ snprintf_default.c snprintf_xml.c \ objopt.c \ compare.c \ - copy.c + copy.c \ + filter.c bsf.c diff --git a/src/conntrack/api.c b/src/conntrack/api.c index 58efd32..3bd96a8 100644 --- a/src/conntrack/api.c +++ b/src/conntrack/api.c @@ -842,3 +842,96 @@ void nfct_copy_attr(struct nf_conntrack *ct1, set_bit(type, ct1->set); } } + +/** + * nfct_filter_create - create a filter + * + * This function returns a valid pointer on success, otherwise NULL is + * returned and errno is appropriately set. + */ +struct nfct_filter *nfct_filter_create(void) +{ + return calloc(sizeof(struct nfct_filter), 1); +} + +/** + * nfct_filter_destroy - destroy a filter + * @filter: filter that we want to destroy + * + * This function releases the memory that is used by the filter object. + * However, please note that this function does *not* detach an already + * attached filter. + */ +void nfct_filter_destroy(struct nfct_filter *filter) +{ + assert(filter != NULL); + free(filter); + filter = NULL; +} + +/** + * nfct_filter_add_attr - add a filter attribute of the filter object + * @filter: filter object that we want to modify + * @type: filter attribute type + * @value: pointer to the value of the filter attribute + * + * Limitations: You can add up to 256 IPv4 addresses and masks for + * NFCT_FILTER_SRC_IPV4 and, similarly, 256 for NFCT_FILTER_DST_IPV4. + */ +void nfct_filter_add_attr(struct nfct_filter *filter, + const enum nfct_filter_attr type, + const void *value) +{ + assert(filter != NULL); + assert(value != NULL); + + if (type >= NFCT_FILTER_MAX) + return; + + if (filter_attr_array[type]) { + filter_attr_array[type](filter, value); + set_bit(type, filter->set); + } +} + +/** + * nfct_filter_add_attr_u32 - add an u32 filter attribute of the filter object + * @filter: filter object that we want to modify + * @type: filter attribute type + * @value: value of the filter attribute using unsigned int (32 bits). + */ +void nfct_filter_add_attr_u32(struct nfct_filter *filter, + const enum nfct_filter_attr type, + u_int32_t value) +{ + nfct_filter_add_attr(filter, type, &value); +} + +/** + * nfct_filter_attach - attach a filter to a socket descriptor + * @fd: socket descriptor + * @filter: filter that we want to attach to the socket + * + * This function returns -1 on error and set errno appropriately. If the + * function returns EINVAL probably you have found a bug in it. Please, + * report this. + */ +int nfct_filter_attach(int fd, struct nfct_filter *filter) +{ + assert(filter != NULL); + + return __setup_netlink_socket_filter(fd, filter); +} + +/** + * nfct_filter_detach - detach an existing filter + * @fd: socket descriptor + * + * This function returns -1 on error and set errno appropriately. + */ +int nfct_filter_detach(int fd) +{ + int val = 0; + + return setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)); +} diff --git a/src/conntrack/bsf.c b/src/conntrack/bsf.c new file mode 100644 index 0000000..2ac5fe1 --- /dev/null +++ b/src/conntrack/bsf.c @@ -0,0 +1,416 @@ +/* + * (C) 2008 by Pablo Neira Ayuso + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#include "internal.h" +#include + +#ifndef SKF_AD_NLATTR +#define SKF_AD_NLATTR 12 +#endif + +#define NFCT_FILTER_REJECT 0U +#define NFCT_FILTER_ACCEPT ~0U + +#if 0 +static void show_filter(struct sock_filter *this, int size) +{ + int i; + + for(i=0; idata[X + k:word_size] */ + .code = BPF_LD|word_size|BPF_IND, + .k = sizeof(struct nfattr), + }, + }; + + memcpy(this, filter, sizeof(filter)); +} + +static int +add_state_filter_cta(struct sock_filter *this, + unsigned int cta_protoinfo_proto, + unsigned int cta_protoinfo_state, + u_int16_t state_flags, + size_t remain) +{ + struct sock_filter filter[14 + __FILTER_PROTO_MAX]; + struct sock_filter verdict = { + /* Reject */ + .code = BPF_RET|BPF_K, + .k = NFCT_FILTER_REJECT, + }; + unsigned int i, j; + unsigned int label_continue; + + /* calculate the number of filter lines */ + for (i = 0, j = 0; i < sizeof(state_flags) * 8; i++) { + if (state_flags & (1 << i)) { + j++; + } + } + + /* nothing to filter, skip */ + if (j == 0) + return 0; + + if (j + 14 >= __FILTER_PROTO_MAX + 14 || j + 14 > remain) { + errno = ENOSPC; + return -1; + } + + memset(filter, 0, sizeof(filter)); + + label_continue = j + 1; + + set_basic_filter(filter, + CTA_PROTOINFO, + cta_protoinfo_proto, + cta_protoinfo_state, + 14 + label_continue, + BPF_B); + + for (i = 0, j = 0; i < sizeof(state_flags) * 8; i++) { + struct sock_filter cmp = { + .code = BPF_JMP|BPF_JEQ|BPF_K, + .k = i, + .jt = label_continue - j - 1, + }; + + if (state_flags & (1 << i)) { + memcpy(&filter[j + 14], &cmp, sizeof(cmp)); + j++; + } + } + + memcpy(this, filter, sizeof(struct sock_filter) * (j + 14)); + memcpy(&this[j + 14], &verdict, sizeof(verdict)); + + return j + 14 + 1; +} + +static int +add_state_filter(struct sock_filter *this, + int proto, + u_int16_t flags, + size_t remain) +{ + struct { + unsigned int cta_protoinfo; + unsigned int cta_state; + } cta[IPPROTO_MAX] = { + [IPPROTO_TCP] = { + .cta_protoinfo = CTA_PROTOINFO_TCP, + .cta_state = CTA_PROTOINFO_TCP_STATE, + }, + [IPPROTO_SCTP] = { + .cta_protoinfo = CTA_PROTOINFO_SCTP, + .cta_state = CTA_PROTOINFO_SCTP_STATE, + }, + [IPPROTO_DCCP] = { + .cta_protoinfo = CTA_PROTOINFO_DCCP, + .cta_state = CTA_PROTOINFO_DCCP_STATE, + }, + }; + + if (cta[proto].cta_protoinfo == 0 && cta[proto].cta_state == 0) { + errno = ENOTSUP; + return -1; + } + + return add_state_filter_cta(this, + cta[proto].cta_protoinfo, + cta[proto].cta_state, + flags, + remain); +} + +static int +bsf_add_state_filter(const struct nfct_filter *filter, + struct sock_filter *this, + size_t remain) +{ + unsigned int i, j; + + for (i = 0, j = 0; i < IPPROTO_MAX; i++) { + if (test_bit(i, filter->l4proto_map) && + filter->l4proto_state[i].map) { + j += add_state_filter(this, + i, + filter->l4proto_state[i].map, + remain); + } + } + + return j; +} + +static int +bsf_add_proto_filter(const struct nfct_filter *f, + struct sock_filter *this, + size_t remain) +{ + struct sock_filter filter[14 + IPPROTO_MAX]; + struct sock_filter verdict = { + /* Reject */ + .code = BPF_RET|BPF_K, + .k = NFCT_FILTER_REJECT, + }; + unsigned int i, j; + unsigned int label_continue; + + for (i = 0, j = 0; i < IPPROTO_MAX; i++) { + if (test_bit(i, f->l4proto_map)) { + j++; + } + } + + /* nothing to filter, skip */ + if (j == 0) + return 0; + + if (j + 14 >= IPPROTO_MAX + 14 || j + 14 > remain) { + errno = ENOSPC; + return -1; + } + + label_continue = j + 1; + + memset(filter, 0, sizeof(filter)); + + set_basic_filter(filter, + CTA_TUPLE_ORIG, + CTA_TUPLE_PROTO, + CTA_PROTO_NUM, + 14 + label_continue, + BPF_B); + + for (i = 0, j = 0; i < IPPROTO_MAX; i++) { + struct sock_filter cmp = { + .code = BPF_JMP|BPF_JEQ|BPF_K, + .k = i, + .jt = label_continue - j - 1, + }; + + if (test_bit(i, f->l4proto_map)) { + memcpy(&filter[j + 14], &cmp, sizeof(cmp)); + j++; + } + } + + memcpy(this, filter, sizeof(struct sock_filter) * (j + 14)); + memcpy(&this[j + 14], &verdict, sizeof(verdict)); + + return j + 14 + 1; +} + +static int +bsf_add_addr_ipv4_filter(const struct nfct_filter *f, + struct sock_filter *this, + unsigned int type, + size_t remain) +{ + struct sock_filter filter[14 + __FILTER_ADDR_MAX]; + struct sock_filter verdict = { + /* Reject */ + .code = BPF_RET|BPF_K, + .k = NFCT_FILTER_REJECT, + }; + unsigned int i, j, dir; + unsigned int label_continue; + + switch(type) { + case CTA_IP_V4_SRC: + dir = __FILTER_ADDR_SRC; + break; + case CTA_IP_V4_DST: + dir = __FILTER_ADDR_DST; + break; + default: + return 0; + } + + /* nothing to filter, skip */ + if (f->l3proto_elems[dir] == 0) + return 0; + + if (f->l3proto_elems[dir] + 14 >= __FILTER_ADDR_MAX + 14 || + f->l3proto_elems[dir] + 14 > remain) { + errno = ENOSPC; + return -1; + } + + label_continue = (f->l3proto_elems[dir] * 2) + 1; + + memset(filter, 0, sizeof(filter)); + + set_basic_filter(filter, + CTA_TUPLE_ORIG, + CTA_TUPLE_IP, + type, + 14 + label_continue, + BPF_W); + + for (i = 0, j = 0; i < f->l3proto_elems[dir]; i++) { + struct sock_filter cmp[] = { + [0] = { + .code = BPF_ALU|BPF_AND|BPF_K, + .k = f->l3proto[dir][i].mask, + }, + [1] = { + .code = BPF_JMP|BPF_JEQ|BPF_K, + .k = f->l3proto[dir][i].addr & + f->l3proto[dir][i].mask, + .jt = label_continue - j - 2, + }, + }; + memcpy(&filter[j + 14], cmp, sizeof(cmp)); + j+=2; + } + + memcpy(this, filter, sizeof(struct sock_filter) * (j + 14)); + memcpy(&this[j + 14], &verdict, sizeof(verdict)); + + return j + 14 + 1; +} + +static int +bsf_add_saddr_ipv4_filter(const struct nfct_filter *f, + struct sock_filter *this, + size_t remain) +{ + return bsf_add_addr_ipv4_filter(f, this, CTA_IP_V4_SRC, remain); +} + +static int +bsf_add_daddr_ipv4_filter(const struct nfct_filter *f, + struct sock_filter *this, + size_t remain) +{ + return bsf_add_addr_ipv4_filter(f, this, CTA_IP_V4_DST, remain); +} + +/* this buffer must be big enough to store all the autogenerated lines */ +#define BSF_BUFFER_SIZE 1024 + +int __setup_netlink_socket_filter(int fd, struct nfct_filter *f) +{ + struct sock_filter bsf[BSF_BUFFER_SIZE]; + struct sock_filter bsf_accept = { + /* Accept */ + .code = BPF_RET|BPF_K, + .k = NFCT_FILTER_ACCEPT, + }; + struct sock_fprog sf; + unsigned int j = 0; + + memset(bsf, 0, sizeof(bsf)); + + j += bsf_add_proto_filter(f, &bsf[j], BSF_BUFFER_SIZE-j); + j += bsf_add_saddr_ipv4_filter(f, &bsf[j], BSF_BUFFER_SIZE-j); + j += bsf_add_daddr_ipv4_filter(f, &bsf[j], BSF_BUFFER_SIZE-j); + j += bsf_add_state_filter(f, &bsf[j], BSF_BUFFER_SIZE-j); + + /* nothing to filter, skip */ + if (j == 0) + return 0; + + memcpy(&bsf[j], &bsf_accept, sizeof(struct sock_filter)); + + show_filter(bsf, j+1); + + sf.len = (sizeof(struct sock_filter) * (j + 1)) / sizeof(bsf[0]); + sf.filter = bsf; + + return setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &sf, sizeof(sf)); +} diff --git a/src/conntrack/filter.c b/src/conntrack/filter.c new file mode 100644 index 0000000..5ea7d5e --- /dev/null +++ b/src/conntrack/filter.c @@ -0,0 +1,39 @@ +#include "internal.h" + +static void filter_attr_l4proto(struct nfct_filter *filter, const void *value) +{ + set_bit(*((int *) value), filter->l4proto_map); +} + +static void +filter_attr_l4proto_state(struct nfct_filter *filter, const void *value) +{ + const struct nfct_filter_proto *this = value; + + set_bit_u16(this->state, &filter->l4proto_state[this->proto].map); +} + +static void filter_attr_src_ipv4(struct nfct_filter *filter, const void *value) +{ + const struct nfct_filter_ipv4 *this = value; + + filter->l3proto[0][filter->l3proto_elems[0]].addr = this->addr; + filter->l3proto[0][filter->l3proto_elems[0]].mask = this->mask; + filter->l3proto_elems[0]++; +} + +static void filter_attr_dst_ipv4(struct nfct_filter *filter, const void *value) +{ + const struct nfct_filter_ipv4 *this = value; + + filter->l3proto[1][filter->l3proto_elems[1]].addr = this->addr; + filter->l3proto[1][filter->l3proto_elems[1]].mask = this->mask; + filter->l3proto_elems[1]++; +} + +filter_attr filter_attr_array[] = { + [NFCT_FILTER_L4PROTO] = filter_attr_l4proto, + [NFCT_FILTER_L4PROTO_STATE] = filter_attr_l4proto_state, + [NFCT_FILTER_SRC_IPV4] = filter_attr_src_ipv4, + [NFCT_FILTER_DST_IPV4] = filter_attr_dst_ipv4, +}; diff --git a/utils/Makefile.am b/utils/Makefile.am index bdf3833..b0797ae 100644 --- a/utils/Makefile.am +++ b/utils/Makefile.am @@ -5,7 +5,7 @@ check_PROGRAMS = expect_dump expect_create expect_get expect_delete \ conntrack_create conntrack_dump conntrack_update \ conntrack_delete conntrack_flush conntrack_create_nat \ conntrack_get conntrack_events \ - conntrack_master + conntrack_master conntrack_filter conntrack_create_SOURCES = conntrack_create.c conntrack_create_LDADD = ../src/libnetfilter_conntrack.la @@ -39,6 +39,10 @@ conntrack_events_SOURCES = conntrack_events.c conntrack_events_LDADD = ../src/libnetfilter_conntrack.la conntrack_events_LDFLAGS = -dynamic -ldl +conntrack_filter_SOURCES = conntrack_filter.c +conntrack_filter_LDADD = ../src/libnetfilter_conntrack.la +conntrack_filter_LDFLAGS = -dynamic -ldl + conntrack_master_SOURCES = conntrack_master.c conntrack_master_LDADD = ../src/libnetfilter_conntrack.la conntrack_master_LDFLAGS = -dynamic -ldl diff --git a/utils/conntrack_filter.c b/utils/conntrack_filter.c new file mode 100644 index 0000000..7d22950 --- /dev/null +++ b/utils/conntrack_filter.c @@ -0,0 +1,83 @@ +#include +#include +#include + +#include +#include + +static int event_cb(enum nf_conntrack_msg_type type, + struct nf_conntrack *ct, + void *data) +{ + static int n = 0; + char buf[1024]; + + nfct_snprintf(buf, 1024, ct, type, NFCT_O_PLAIN, NFCT_OF_TIME); + printf("%s\n", buf); + + if (++n == 10) + return NFCT_CB_STOP; + + return NFCT_CB_CONTINUE; +} + +int main() +{ + int ret; + u_int8_t family = AF_INET; + struct nfct_handle *h; + struct nfct_filter *filter; + struct nf_conntrack *ct; + char buf[1024]; + + h = nfct_open(CONNTRACK, NF_NETLINK_CONNTRACK_NEW | + NF_NETLINK_CONNTRACK_UPDATE); + if (!h) { + perror("nfct_open"); + return 0; + } + + filter = nfct_filter_create(); + if (!filter) { + perror("nfct_create_filter"); + return 0; + } + + nfct_filter_add_attr_u32(filter, NFCT_FILTER_L4PROTO, IPPROTO_UDP); + nfct_filter_add_attr_u32(filter, NFCT_FILTER_L4PROTO, IPPROTO_TCP); + + struct nfct_filter_proto filter_proto = { + .proto = IPPROTO_TCP, + .state = TCP_CONNTRACK_ESTABLISHED + }; + + nfct_filter_add_attr(filter, NFCT_FILTER_L4PROTO_STATE, &filter_proto); + + struct nfct_filter_ipv4 filter_ipv4 = { + .addr = htonl(inet_addr("127.0.0.1")), + .mask = 0xffffffff, + }; + + nfct_filter_add_attr(filter, NFCT_FILTER_SRC_IPV4, &filter_ipv4); + + if (nfct_filter_attach(nfct_fd(h), filter) == -1) { + perror("nfct_filter_attach"); + return 0; + } + + /* release the filter object, this does not detach the filter */ + nfct_filter_destroy(filter); + + nfct_callback_register(h, NFCT_T_ALL, event_cb, NULL); + + printf("TEST: waiting for 10 events...\n"); + + ret = nfct_catch(h); + + printf("TEST: OK (%d)(%s)\n", ret, strerror(errno)); + + if (ret == -1) + exit(EXIT_FAILURE); + + nfct_close(h); +} -- cgit v1.2.3