diff --git a/configure.ac b/configure.ac index 11716d886..cb47c69bd 100644 --- a/configure.ac +++ b/configure.ac @@ -247,6 +247,7 @@ ARG_ENABL_SET([tnccs-dynamic], [enable dynamic TNCCS protocol discovery module. ARG_ENABL_SET([android-log], [enable Android specific logger plugin.]) ARG_ENABL_SET([certexpire], [enable CSV export of expiration dates of used certificates.]) ARG_ENABL_SET([connmark], [enable connmark plugin using conntrack based marks to select return path SA.]) +ARG_ENABL_SET([forecast], [enable forecast plugin forwarding broadcast/multicast messages.]) ARG_ENABL_SET([duplicheck], [advanced duplicate checking plugin using liveness checks.]) ARG_ENABL_SET([error-notify], [enable error notification plugin.]) ARG_ENABL_SET([farp], [enable ARP faking plugin that responds to ARP requests to peers virtual IP]) @@ -1270,6 +1271,7 @@ ADD_PLUGIN([socket-default], [c charon nm cmd]) ADD_PLUGIN([socket-dynamic], [c charon cmd]) ADD_PLUGIN([socket-win], [c charon]) ADD_PLUGIN([connmark], [c charon]) +ADD_PLUGIN([forecast], [c charon]) ADD_PLUGIN([farp], [c charon]) ADD_PLUGIN([stroke], [c charon]) ADD_PLUGIN([vici], [c charon]) @@ -1478,6 +1480,7 @@ AM_CONDITIONAL(USE_SOCKET_DEFAULT, test x$socket_default = xtrue) AM_CONDITIONAL(USE_SOCKET_DYNAMIC, test x$socket_dynamic = xtrue) AM_CONDITIONAL(USE_SOCKET_WIN, test x$socket_win = xtrue) AM_CONDITIONAL(USE_CONNMARK, test x$connmark = xtrue) +AM_CONDITIONAL(USE_FORECAST, test x$forecast = xtrue) AM_CONDITIONAL(USE_FARP, test x$farp = xtrue) AM_CONDITIONAL(USE_ADDRBLOCK, test x$addrblock = xtrue) AM_CONDITIONAL(USE_UNITY, test x$unity = xtrue) @@ -1713,6 +1716,7 @@ AC_CONFIG_FILES([ src/libcharon/plugins/socket_dynamic/Makefile src/libcharon/plugins/socket_win/Makefile src/libcharon/plugins/connmark/Makefile + src/libcharon/plugins/forecast/Makefile src/libcharon/plugins/farp/Makefile src/libcharon/plugins/smp/Makefile src/libcharon/plugins/sql/Makefile diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index 2ce463533..7d6e86bd5 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -216,6 +216,13 @@ if MONOLITHIC endif endif +if USE_FORECAST + SUBDIRS += plugins/forecast +if MONOLITHIC + libcharon_la_LIBADD += plugins/forecast/libstrongswan-forecast.la +endif +endif + if USE_FARP SUBDIRS += plugins/farp if MONOLITHIC diff --git a/src/libcharon/plugins/forecast/Makefile.am b/src/libcharon/plugins/forecast/Makefile.am new file mode 100644 index 000000000..fc08eecc4 --- /dev/null +++ b/src/libcharon/plugins/forecast/Makefile.am @@ -0,0 +1,21 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/libstrongswan \ + -I$(top_srcdir)/src/libhydra \ + -I$(top_srcdir)/src/libcharon + +AM_CFLAGS = \ + $(PLUGIN_CFLAGS) + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-forecast.la +else +plugin_LTLIBRARIES = libstrongswan-forecast.la +endif + +libstrongswan_forecast_la_SOURCES = \ + forecast_listener.h forecast_listener.c \ + forecast_forwarder.h forecast_forwarder.c \ + forecast_plugin.h forecast_plugin.c + +libstrongswan_forecast_la_LDFLAGS = -module -avoid-version +libstrongswan_forecast_la_LIBADD = -lip4tc diff --git a/src/libcharon/plugins/forecast/forecast_forwarder.c b/src/libcharon/plugins/forecast/forecast_forwarder.c new file mode 100644 index 000000000..07a3d4953 --- /dev/null +++ b/src/libcharon/plugins/forecast/forecast_forwarder.c @@ -0,0 +1,496 @@ +/* + * Copyright (C) 2010-2014 Martin Willi + * Copyright (C) 2010-2014 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program 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 General Public License + * for more details. + */ + +#include "forecast_forwarder.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define BOOTP_SERVER_PORT 67 +#define BOOTP_CLIENT_PORT 68 + +typedef struct private_forecast_forwarder_t private_forecast_forwarder_t; +typedef struct private_kernel_listener_t private_kernel_listener_t; + +/** + * Private data of registered kernel listener + */ +struct private_kernel_listener_t { + + /** + * Implements kernel_listener_t + */ + kernel_listener_t listener; + + /** + * Listener that knows active addresses + */ + forecast_listener_t *fc; + + /** + * current broadcast address of internal network + */ + u_int32_t broadcast; + + /** + * LAN interface index + */ + int ifindex; + + /** + * Packet socket + */ + int pkt; + + /** + * RAW socket + */ + int raw; +}; + +/** + * Private data of an forecast_forwarder_t object. + */ +struct private_forecast_forwarder_t { + + /** + * Public forecast_forwarder_t interface. + */ + forecast_forwarder_t public; + + /** + * Public kernel_listener_t interface. + */ + private_kernel_listener_t kernel; +}; + +/** + * Send a broadcast/multicast packet to a network + */ +static void send_net(private_forecast_forwarder_t *this, + struct sockaddr_ll *addr, void *buf, size_t len) +{ + if (sendto(this->kernel.pkt, buf, len, 0, + (struct sockaddr*)addr, sizeof(*addr)) != len) + { + DBG1(DBG_NET, "forecast send_net() failed: %s", strerror(errno)); + } +} + +/** + * Send a broadcast/multicast packet to a peer + */ +static void send_peer(private_forecast_forwarder_t *this, u_int32_t dst, + void *buf, size_t len, int mark) +{ + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr.s_addr = dst, + }; + + if (setsockopt(this->kernel.raw, SOL_SOCKET, SO_MARK, + &mark, sizeof(mark)) != 0) + { + DBG1(DBG_NET, "forecast setting SO_MARK failed: %s", strerror(errno)); + } + if (sendto(this->kernel.raw, buf, len, 0, + (struct sockaddr*)&addr, sizeof(addr)) != len) + { + DBG1(DBG_NET, "forecast send_peer() failed: %s", strerror(errno)); + } +} + +/** + * Check if an IP packet is BOOTP/DHCP + */ +static bool is_bootp(void *buf, size_t len) +{ + struct __attribute__((__packed__)) { + struct iphdr ip; + struct udphdr udp; + } *pkt = buf; + + if (len > sizeof(*pkt)) + { + if (ntohs(pkt->udp.source) == BOOTP_CLIENT_PORT && + ntohs(pkt->udp.dest) == BOOTP_SERVER_PORT) + { + return TRUE; + } + if (ntohs(pkt->udp.source) == BOOTP_SERVER_PORT && + ntohs(pkt->udp.dest) == BOOTP_CLIENT_PORT) + { + return TRUE; + } + } + return FALSE; +} + +/** + * Broadcast/Multicast receiver + */ +static bool receive_casts(private_forecast_forwarder_t *this) +{ + struct __attribute__((packed)) { + struct iphdr hdr; + char data[2048]; + } buf; + char *type; + ssize_t len; + u_int mark, origin = 0; + host_t *src, *dst; + traffic_selector_t *ts; + enumerator_t *enumerator; + struct sockaddr_ll addr; + socklen_t alen = sizeof(addr); + bool reinject; + + len = recvfrom(this->kernel.pkt, &buf, sizeof(buf), MSG_DONTWAIT, + (struct sockaddr*)&addr, &alen); + if (len < 0) + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + { + DBG1(DBG_NET, "receiving from forecast socket failed: %s", + strerror(errno)); + } + return TRUE; + } + else if (len < sizeof(struct iphdr)) + { + DBG1(DBG_NET, "received short forecast packet: %zd bytes", len); + return TRUE; + } + if (is_bootp(&buf, len)) + { /* don't forward DHCP broadcasts */ + return TRUE; + } + + src = host_create_from_chunk(AF_INET, chunk_from_thing(buf.hdr.saddr), 0); + dst = host_create_from_chunk(AF_INET, chunk_from_thing(buf.hdr.daddr), 0); + + /* create valid broadcast/multicast MAC to send out */ + if (IN_MULTICAST(ntohl(buf.hdr.daddr))) + { + type = "multi"; + ETHER_MAP_IP_MULTICAST(&buf.hdr.daddr, addr.sll_addr); + } + else + { + type = "broad"; + memset(&addr.sll_addr, 0xFF, sizeof(addr.sll_addr)); + } + DBG2(DBG_NET, "forecast intercepted packet: %H to %H", src, dst); + + /* find mark of originating tunnel */ + enumerator = this->kernel.fc->create_enumerator(this->kernel.fc, FALSE); + while (enumerator->enumerate(enumerator, &ts, &mark, &reinject)) + { + if (ts->includes(ts, src)) + { + origin = mark; + break; + } + } + enumerator->destroy(enumerator); + + /* send packet over all tunnels, but not the packets origin */ + enumerator = this->kernel.fc->create_enumerator(this->kernel.fc, FALSE); + while (enumerator->enumerate(enumerator, &ts, &mark, &reinject)) + { + if (ts->includes(ts, dst)) + { + if ((reinject && origin != mark) || origin == 0) + { + DBG2(DBG_NET, "forwarding a %H %scast from %H to peer %R (%u)", + dst, type, src, ts, mark); + send_peer(this, buf.hdr.daddr, &buf, len, mark); + } + } + } + enumerator->destroy(enumerator); + + if (origin) + { + /* forward broadcast/multicast from client to network */ + DBG2(DBG_NET, "forwarding a %H %scast from peer %H to internal network", + dst, type, src); + addr.sll_ifindex = this->kernel.ifindex; + send_net(this, &addr, &buf, len); + } + + dst->destroy(dst); + src->destroy(src); + + return TRUE; +} + +/** + * Join a multicast group + */ +static void join_group(private_kernel_listener_t *this, char *group, + struct sockaddr *addr) +{ + struct sockaddr_in *in; + struct ip_mreqn mreq; + host_t *host; + + host = host_create_from_string(group, 0); + if (host) + { + memset(&mreq, 0, sizeof(mreq)); + memcpy(&mreq.imr_multiaddr.s_addr, host->get_address(host).ptr, 4); + if (addr->sa_family == AF_INET) + { + in = (struct sockaddr_in*)addr; + memcpy(&mreq.imr_address, &in->sin_addr.s_addr, + sizeof(in->sin_addr.s_addr)); + } + mreq.imr_ifindex = this->ifindex; + if (setsockopt(this->raw, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &mreq, sizeof(mreq)) == -1) + { + if (errno != EADDRINUSE) + { + DBG1(DBG_NET, "forecast multicast join to %s failed: %s", + group, strerror(errno)); + } + } + else + { + DBG2(DBG_NET, "forwarding multicast group %s", group); + } + host->destroy(host); + } +} + +/** + * (Re-)Join all multicast groups we want to forward + */ +static void join_groups(private_kernel_listener_t *this, struct sockaddr *addr) +{ + enumerator_t *enumerator; + char *groups, *group; + static char *def = + "224.0.0.1," /* host multicast */ + "224.0.0.22," /* IGMP */ + "224.0.0.251," /* mDNS */ + "224.0.0.252," /* LLMNR */ + "239.255.255.250"; /* SSDP/WS-discovery */ + + groups = lib->settings->get_str(lib->settings, + "%s.plugins.forecast.groups", def, lib->ns); + DBG1(DBG_CFG, "joining forecast multicast groups: %s", groups); + enumerator = enumerator_create_token(groups, ",", " "); + while (enumerator->enumerate(enumerator, &group)) + { + join_group(this, group, addr); + } + enumerator->destroy(enumerator); +} + +/** + * Attach the socket filter to the socket + */ +static bool attach_filter(int fd, u_int32_t broadcast) +{ + struct sock_filter filter_code[] = { + /* destination address: is ... */ + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct iphdr, daddr)), + /* broadcast, as received from the local network */ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ntohl(broadcast), 4, 0), + /* broadcast, as Win7 sends them */ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0xFFFFFFFF, 3, 0), + /* any multicast, 224.0.0.0/4 */ + BPF_STMT(BPF_ALU+BPF_AND+BPF_K, 0xF0000000), + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0xE0000000, 1, 0), + BPF_STMT(BPF_RET+BPF_K, 0), + BPF_STMT(BPF_LD+BPF_W+BPF_LEN, 0), + BPF_STMT(BPF_RET+BPF_A, 0), + }; + struct sock_fprog filter = { + sizeof(filter_code) / sizeof(struct sock_filter), + filter_code, + }; + + if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, + &filter, sizeof(filter)) < 0) + { + DBG1(DBG_NET, "installing forecast PACKET socket filter failed: %s", + strerror(errno)); + return FALSE; + } + return TRUE; +} + +/** + * Get the interface index of an interface name + */ +static int get_ifindex(private_kernel_listener_t *this, char *ifname) +{ + struct ifreq ifr = {}; + + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + if (ioctl(this->raw, SIOCGIFINDEX, &ifr) == 0) + { + return ifr.ifr_ifindex; + } + return 0; +} + +/** + * Set up the interface for broad/multicast forwarding + */ +static void setup_interface(private_kernel_listener_t *this) +{ + struct ifaddrs *addrs, *current; + struct sockaddr_in *in; + host_t *host; + char *name; + + name = lib->settings->get_str(lib->settings, + "%s.plugins.forecast.interface", NULL, lib->ns); + if (getifaddrs(&addrs) == 0) + { + for (current = addrs; current; current = current->ifa_next) + { + if (name && !streq(name, current->ifa_name)) + { + continue; + } + if (current->ifa_flags & IFF_BROADCAST && + current->ifa_broadaddr && + current->ifa_broadaddr->sa_family == AF_INET) + { + DBG1(DBG_NET, "using forecast interface %s", current->ifa_name); + this->ifindex = get_ifindex(this, current->ifa_name); + in = (struct sockaddr_in*)current->ifa_broadaddr; + attach_filter(this->pkt, in->sin_addr.s_addr); + join_groups(this, current->ifa_addr); + host = host_create_from_sockaddr(current->ifa_broadaddr); + if (host) + { + this->fc->set_broadcast(this->fc, host); + host->destroy(host); + } + break; + } + } + } + freeifaddrs(addrs); +} + +METHOD(kernel_listener_t, roam, bool, + private_kernel_listener_t *this, bool address) +{ + if (address) + { + setup_interface(this); + } + return TRUE; +} + +METHOD(forecast_forwarder_t, destroy, void, + private_forecast_forwarder_t *this) +{ + if (this->kernel.raw != -1) + { + close(this->kernel.raw); + } + if (this->kernel.pkt != -1) + { + lib->watcher->remove(lib->watcher, this->kernel.pkt); + close(this->kernel.pkt); + } + hydra->kernel_interface->remove_listener(hydra->kernel_interface, + &this->kernel.listener); + free(this); +} + +/** + * See header + */ +forecast_forwarder_t *forecast_forwarder_create(forecast_listener_t *listener) +{ + private_forecast_forwarder_t *this; + int on = 1; + + INIT(this, + .public = { + .destroy = _destroy, + }, + .kernel = { + .listener = { + .roam = _roam, + }, + .raw = -1, + .pkt = -1, + .fc = listener, + }, + ); + + this->kernel.pkt = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); + if (this->kernel.pkt == -1) + { + DBG1(DBG_NET, "opening PACKET socket failed: %s", strerror(errno)); + destroy(this); + return NULL; + } + this->kernel.raw = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); + if (this->kernel.raw == -1) + { + DBG1(DBG_NET, "opening RAW socket failed: %s", strerror(errno)); + destroy(this); + return NULL; + } + if (setsockopt(this->kernel.raw, IPPROTO_IP, IP_HDRINCL, + &on, sizeof(on)) == -1) + { + DBG1(DBG_NET, "forecast socket HDRINCL failed: %s", strerror(errno)); + destroy(this); + return NULL; + } + if (setsockopt(this->kernel.raw, SOL_SOCKET, SO_BROADCAST, + &on, sizeof(on)) == -1) + { + DBG1(DBG_NET, "forecast socket BROADCAST failed: %s", strerror(errno)); + destroy(this); + return NULL; + } + + setup_interface(&this->kernel); + + hydra->kernel_interface->add_listener(hydra->kernel_interface, + &this->kernel.listener); + + lib->watcher->add(lib->watcher, this->kernel.pkt, WATCHER_READ, + (watcher_cb_t)receive_casts, this); + + return &this->public; +} diff --git a/src/libcharon/plugins/forecast/forecast_forwarder.h b/src/libcharon/plugins/forecast/forecast_forwarder.h new file mode 100644 index 000000000..14d107361 --- /dev/null +++ b/src/libcharon/plugins/forecast/forecast_forwarder.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010-2014 Martin Willi + * Copyright (C) 2010-2014 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program 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 General Public License + * for more details. + */ + +/** + * @defgroup forecast_forwarder forecast_forwarder + * @{ @ingroup forecast + */ + +#ifndef FORECAST_FORWARDER_H_ +#define FORECAST_FORWARDER_H_ + +#include "forecast_listener.h" + +typedef struct forecast_forwarder_t forecast_forwarder_t; + +/** + * Broadcast/Multicast sniffer and forwarder. + */ +struct forecast_forwarder_t { + + /** + * Destroy a forecast_forwarder_t. + */ + void (*destroy)(forecast_forwarder_t *this); +}; + +/** + * Create a forecast_forwarder instance. + * + * @param listener listener to check for addresses to forward to + * @return forwarder instance + */ +forecast_forwarder_t *forecast_forwarder_create(forecast_listener_t *listener); + +#endif /** FORECAST_FORWARDER_H_ @}*/ diff --git a/src/libcharon/plugins/forecast/forecast_listener.c b/src/libcharon/plugins/forecast/forecast_listener.c new file mode 100644 index 000000000..186378be1 --- /dev/null +++ b/src/libcharon/plugins/forecast/forecast_listener.c @@ -0,0 +1,680 @@ +/* + * Copyright (C) 2010-2014 Martin Willi + * Copyright (C) 2010-2014 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program 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 General Public License + * for more details. + */ + +#include "forecast_listener.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +typedef struct private_forecast_listener_t private_forecast_listener_t; + +/** + * Private data of an forecast_listener_t object. + */ +struct private_forecast_listener_t { + + /** + * Public forecast_listener_t interface. + */ + forecast_listener_t public; + + /** + * List of entries + */ + linked_list_t *entries; + + /** + * RWlock for IP list + */ + rwlock_t *lock; + + /** + * Configs we do reinjection + */ + char *reinject_configs; + + /** + * Broadcast address on LAN interface, network order + */ + u_int32_t broadcast; +}; + +/** + * Hashtable entry + */ +typedef struct { + /** local traffic selectors */ + array_t *lts; + /** remote traffic selectors */ + array_t *rts; + /** firewall mark used by CHILD_SA */ + u_int mark; + /** local IKE_SA endpoint */ + host_t *lhost; + /** remote IKE_SA endpoint */ + host_t *rhost; + /** inbound SPI */ + u_int32_t spi; + /** use UDP encapsulation */ + bool encap; + /** wheter we should allow reencapsulation of IPsec received forecasts */ + bool reinject; + /** broadcast address used for that entry */ + u_int32_t broadcast; +} entry_t; + +/** + * Destroy an entry + */ +static void entry_destroy(entry_t *entry) +{ + if (entry) + { + entry->lhost->destroy(entry->lhost); + entry->rhost->destroy(entry->rhost); + array_destroy_offset(entry->lts, offsetof(traffic_selector_t, destroy)); + array_destroy_offset(entry->rts, offsetof(traffic_selector_t, destroy)); + free(entry); + } +} + +/** + * Convert an (IPv4) traffic selector to an address and mask + */ +static bool ts2in(traffic_selector_t *ts, + struct in_addr *addr, struct in_addr *mask) +{ + u_int8_t bits; + host_t *net; + + if (ts->get_type(ts) == TS_IPV4_ADDR_RANGE && + ts->to_subnet(ts, &net, &bits)) + { + memcpy(&addr->s_addr, net->get_address(net).ptr, 4); + net->destroy(net); + mask->s_addr = htonl(0xffffffffU << (32 - bits)); + return TRUE; + } + return FALSE; +} + +/** + * Convert an (IPv4) host to an address with mask + */ +static bool host2in(host_t *host, struct in_addr *addr, struct in_addr *mask) +{ + if (host->get_family(host) == AF_INET) + { + memcpy(&addr->s_addr, host->get_address(host).ptr, 4); + mask->s_addr = ~0; + return TRUE; + } + return FALSE; +} + +/** + * Add or remove a rule to/from the specified chain + */ +static bool manage_rule(struct iptc_handle *ipth, const char *chain, + bool add, struct ipt_entry *e) +{ + if (add) + { + if (!iptc_insert_entry(chain, e, 0, ipth)) + { + DBG1(DBG_CFG, "appending %s rule failed: %s", + chain, iptc_strerror(errno)); + return FALSE; + } + } + else + { + if (!iptc_delete_entry(chain, e, "", ipth)) + { + DBG1(DBG_CFG, "deleting %s rule failed: %s", + chain, iptc_strerror(errno)); + return FALSE; + } + } + return TRUE; +} + +/** + * Add rule marking UDP-encapsulated ESP packets to match the correct policy + */ +static bool manage_pre_esp_in_udp(struct iptc_handle *ipth, + entry_t *entry, bool add) +{ + struct { + struct ipt_entry e; + struct ipt_entry_match m; + struct xt_udp udp; + struct ipt_entry_target t; + struct xt_mark_tginfo2 tm; + } ipt = { + .e = { + .target_offset = XT_ALIGN(sizeof(ipt.e) + sizeof(ipt.m) + + sizeof(ipt.udp)), + .next_offset = sizeof(ipt), + .ip = { + .proto = IPPROTO_UDP, + }, + }, + .m = { + .u = { + .user = { + .match_size = XT_ALIGN(sizeof(ipt.m) + sizeof(ipt.udp)), + .name = "udp", + }, + }, + }, + .udp = { + .spts = { + entry->rhost->get_port(entry->rhost), + entry->rhost->get_port(entry->lhost) + }, + .dpts = { + entry->lhost->get_port(entry->lhost), + entry->lhost->get_port(entry->lhost) + }, + }, + .t = { + .u = { + .user = { + .target_size = XT_ALIGN(sizeof(ipt.t) + sizeof(ipt.tm)), + .name = "MARK", + .revision = 2, + }, + }, + }, + .tm = { + .mark = entry->mark, + .mask = ~0, + }, + }; + + if (!host2in(entry->lhost, &ipt.e.ip.dst, &ipt.e.ip.dmsk) || + !host2in(entry->rhost, &ipt.e.ip.src, &ipt.e.ip.smsk)) + { + return FALSE; + } + return manage_rule(ipth, "PREROUTING", add, &ipt.e); +} + +/** + * Add rule marking non-encapsulated ESP packets to match the correct policy + */ +static bool manage_pre_esp(struct iptc_handle *ipth, entry_t *entry, bool add) +{ + struct { + struct ipt_entry e; + struct ipt_entry_match m; + struct xt_esp esp; + struct ipt_entry_target t; + struct xt_mark_tginfo2 tm; + } ipt = { + .e = { + .target_offset = XT_ALIGN(sizeof(ipt.e) + sizeof(ipt.m) + + sizeof(ipt.esp)), + .next_offset = sizeof(ipt), + .ip = { + .proto = IPPROTO_ESP, + }, + }, + .m = { + .u = { + .user = { + .match_size = XT_ALIGN(sizeof(ipt.m) + sizeof(ipt.esp)), + .name = "esp", + }, + }, + }, + .esp = { + .spis = { htonl(entry->spi), htonl(entry->spi) }, + }, + .t = { + .u = { + .user = { + .target_size = XT_ALIGN(sizeof(ipt.t) + sizeof(ipt.tm)), + .name = "MARK", + .revision = 2, + }, + }, + }, + .tm = { + .mark = entry->mark, + .mask = ~0, + }, + }; + + if (!host2in(entry->lhost, &ipt.e.ip.dst, &ipt.e.ip.dmsk) || + !host2in(entry->rhost, &ipt.e.ip.src, &ipt.e.ip.smsk)) + { + return FALSE; + } + return manage_rule(ipth, "PREROUTING", add, &ipt.e); +} + +/** + * Add rule marking ESP packets to match the correct policy + */ +static bool manage_pre(struct iptc_handle *ipth, entry_t *entry, bool add) +{ + if (entry->encap) + { + return manage_pre_esp_in_udp(ipth, entry, add); + } + return manage_pre_esp(ipth, entry, add); +} + +/** + * Add rule handling outbound traffic to use correct mark + */ +static bool manage_out(struct iptc_handle *ipth, entry_t *entry, bool add) +{ + struct { + struct ipt_entry e; + struct ipt_entry_target t; + struct xt_mark_tginfo2 m; + } ipt = { + .e = { + .target_offset = XT_ALIGN(sizeof(ipt.e)), + .next_offset = sizeof(ipt), + }, + .t = { + .u.user.target_size = XT_ALIGN(sizeof(ipt.t) + sizeof(ipt.m)), + .u.user.name = "MARK", + .u.user.revision = 2, + }, + .m = { + .mark = entry->mark, + .mask = ~0, + }, + }; + enumerator_t *enumerator; + traffic_selector_t *ts; + + enumerator = array_create_enumerator(entry->rts); + while (enumerator->enumerate(enumerator, &ts)) + { + if (!ts2in(ts, &ipt.e.ip.dst, &ipt.e.ip.dmsk)) + { + continue; + } + if (ipt.e.ip.dst.s_addr == 0xffffffff || + ipt.e.ip.dst.s_addr == entry->broadcast || + memeq(&ipt.e.ip.dst.s_addr, "\xe0", 1)) + { + /* skip broadcast/multicast selectors, they are shared and the mark + * is set by the socket we use for reinjection */ + continue; + } + if (!manage_rule(ipth, "PREROUTING", add, &ipt.e) || + !manage_rule(ipth, "OUTPUT", add, &ipt.e)) + { + enumerator->destroy(enumerator); + return FALSE; + } + } + enumerator->destroy(enumerator); + + return TRUE; +} + +/** + * Check if config is whitelisted to reinject traffic + */ +static bool is_reinject_config(private_forecast_listener_t *this, char *name) +{ + enumerator_t *enumerator; + bool reinject = FALSE; + char *token; + + enumerator = enumerator_create_token(this->reinject_configs, ",", " "); + while (enumerator->enumerate(enumerator, &token)) + { + if (streq(token, name)) + { + reinject = TRUE; + break; + } + } + enumerator->destroy(enumerator); + + return reinject; +} + +/** + * Add rules and entry for given CHILD_SA + */ +static bool add_entry(private_forecast_listener_t *this, + struct iptc_handle *ipth, host_t *lhost, host_t *rhost, + child_sa_t *child_sa, bool encap) +{ + enumerator_t *enumerator; + traffic_selector_t *ts; + entry_t *entry; + + INIT(entry, + .lts = array_create(0, 0), + .rts = array_create(0, 0), + .lhost = lhost->clone(lhost), + .rhost = rhost->clone(rhost), + .spi = child_sa->get_spi(child_sa, TRUE), + .encap = encap, + .mark = child_sa->get_mark(child_sa, TRUE).value, + .reinject = is_reinject_config(this, child_sa->get_name(child_sa)), + .broadcast = this->broadcast, + ); + + enumerator = child_sa->create_ts_enumerator(child_sa, TRUE); + while (enumerator->enumerate(enumerator, &ts)) + { + array_insert(entry->lts, ARRAY_TAIL, ts->clone(ts)); + } + enumerator->destroy(enumerator); + + enumerator = child_sa->create_ts_enumerator(child_sa, FALSE); + while (enumerator->enumerate(enumerator, &ts)) + { + array_insert(entry->rts, ARRAY_TAIL, ts->clone(ts)); + } + enumerator->destroy(enumerator); + + if (manage_pre(ipth, entry, TRUE) && + manage_out(ipth, entry, TRUE)) + { + this->lock->write_lock(this->lock); + this->entries->insert_last(this->entries, entry); + this->lock->unlock(this->lock); + return TRUE; + } + entry_destroy(entry); + return FALSE; +} + +/** + * Remove an entry and rules for a given mark + */ +static bool remove_entry(private_forecast_listener_t *this, + struct iptc_handle *ipth, child_sa_t *child_sa) +{ + enumerator_t *enumerator; + entry_t *entry; + bool done = FALSE; + + this->lock->write_lock(this->lock); + enumerator = this->entries->create_enumerator(this->entries); + while (enumerator->enumerate(enumerator, &entry)) + { + if (entry->mark == child_sa->get_mark(child_sa, TRUE).value) + { + this->entries->remove_at(this->entries, enumerator); + if (manage_pre(ipth, entry, FALSE) && + manage_out(ipth, entry, FALSE)) + { + done = TRUE; + } + entry_destroy(entry); + break; + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + + return done; +} + +/** + * Initialize iptables handle, log error + */ +static struct iptc_handle* init_handle() +{ + struct iptc_handle *ipth; + + ipth = iptc_init("mangle"); + if (ipth) + { + return ipth; + } + DBG1(DBG_CFG, "initializing iptables failed: %s", iptc_strerror(errno)); + return NULL; +} + +/** + * Commit iptables rules, log error + */ +static bool commit_handle(struct iptc_handle *ipth) +{ + if (iptc_commit(ipth)) + { + return TRUE; + } + DBG1(DBG_CFG, "forecast iptables commit failed: %s", iptc_strerror(errno)); + return FALSE; +} + +/** + * Check if we should handle the given CHILD_SA + */ +static bool handle_sa(child_sa_t *child_sa) +{ + return child_sa->get_mark(child_sa, TRUE).value && + child_sa->get_mark(child_sa, FALSE).value; +} + +METHOD(listener_t, child_updown, bool, + private_forecast_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, + bool up) +{ + struct iptc_handle *ipth; + host_t *lhost, *rhost; + bool encap; + + lhost = ike_sa->get_my_host(ike_sa); + rhost = ike_sa->get_other_host(ike_sa); + encap = child_sa->has_encap(child_sa); + + if (handle_sa(child_sa)) + { + ipth = init_handle(); + if (ipth) + { + if (up) + { + if (add_entry(this, ipth, lhost, rhost, child_sa, encap)) + { + commit_handle(ipth); + } + } + else + { + if (remove_entry(this, ipth, child_sa)) + { + commit_handle(ipth); + } + } + iptc_free(ipth); + } + } + return TRUE; +} + +METHOD(listener_t, child_rekey, bool, + private_forecast_listener_t *this, ike_sa_t *ike_sa, + child_sa_t *old, child_sa_t *new) +{ + struct iptc_handle *ipth;; + host_t *lhost, *rhost; + + lhost = ike_sa->get_my_host(ike_sa); + rhost = ike_sa->get_other_host(ike_sa); + + if (handle_sa(old)) + { + ipth = init_handle(); + if (ipth) + { + if (remove_entry(this, ipth, old) && + add_entry(this, ipth, lhost, rhost, new, new->has_encap(new))) + { + commit_handle(ipth); + } + iptc_free(ipth); + } + } + return TRUE; +} + +METHOD(listener_t, ike_update, bool, + private_forecast_listener_t *this, ike_sa_t *ike_sa, + bool local, host_t *new) +{ + struct iptc_handle *ipth; + enumerator_t *enumerator; + child_sa_t *child_sa; + host_t *lhost, *rhost; + bool encap; + + if (local) + { + lhost = new; + rhost = ike_sa->get_other_host(ike_sa); + } + else + { + lhost = ike_sa->get_my_host(ike_sa); + rhost = new; + } + /* during ike_update(), has_encap() on the CHILD_SA has not yet been + * updated, but shows the old state. */ + encap = ike_sa->has_condition(ike_sa, COND_NAT_ANY); + + enumerator = ike_sa->create_child_sa_enumerator(ike_sa); + while (enumerator->enumerate(enumerator, &child_sa)) + { + if (handle_sa(child_sa)) + { + ipth = init_handle(); + if (ipth) + { + if (remove_entry(this, ipth, child_sa) && + add_entry(this, ipth, lhost, rhost, child_sa, encap)) + { + commit_handle(ipth); + } + iptc_free(ipth); + } + } + } + enumerator->destroy(enumerator); + + return TRUE; +} + +/** + * Filter to map entries to ts/mark + */ +static bool ts_filter(entry_t *entry, traffic_selector_t **ts, + traffic_selector_t **out, void *dummy, u_int32_t *mark, + void *dummy2, bool *reinject) +{ + *out = *ts; + *mark = entry->mark; + *reinject = entry->reinject; + return TRUE; +} + +/** + * Create inner enumerator over local traffic selectors + */ +static enumerator_t* create_inner_local(entry_t *entry, rwlock_t *lock) +{ + return enumerator_create_filter(array_create_enumerator(entry->lts), + (void*)ts_filter, entry, NULL); +} + +/** + * Create inner enumerator over remote traffic selectors + */ +static enumerator_t* create_inner_remote(entry_t *entry, rwlock_t *lock) +{ + return enumerator_create_filter(array_create_enumerator(entry->rts), + (void*)ts_filter, entry, NULL); +} + +METHOD(forecast_listener_t, create_enumerator, enumerator_t*, + private_forecast_listener_t *this, bool local) +{ + this->lock->read_lock(this->lock); + return enumerator_create_nested( + this->entries->create_enumerator(this->entries), + (void*)(local ? create_inner_local : create_inner_remote), + this->lock, (void*)this->lock->unlock); +} + +METHOD(forecast_listener_t, set_broadcast, void, + private_forecast_listener_t *this, host_t *bcast) +{ + if (bcast->get_family(bcast) == AF_INET) + { + struct sockaddr_in *in; + + in = bcast->get_sockaddr(bcast); + this->broadcast = in->sin_addr.s_addr; + } +} + +METHOD(forecast_listener_t, destroy, void, + private_forecast_listener_t *this) +{ + this->entries->destroy(this->entries); + this->lock->destroy(this->lock); + free(this); +} + +/** + * See header + */ +forecast_listener_t *forecast_listener_create() +{ + private_forecast_listener_t *this; + + INIT(this, + .public = { + .listener = { + .ike_update = _ike_update, + .child_updown = _child_updown, + .child_rekey = _child_rekey, + }, + .create_enumerator = _create_enumerator, + .set_broadcast = _set_broadcast, + .destroy = _destroy, + }, + .entries = linked_list_create(), + .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), + .reinject_configs = lib->settings->get_str(lib->settings, + "%s.plugins.forecast.reinject", "", lib->ns), + ); + + return &this->public; +} diff --git a/src/libcharon/plugins/forecast/forecast_listener.h b/src/libcharon/plugins/forecast/forecast_listener.h new file mode 100644 index 000000000..49827ecb1 --- /dev/null +++ b/src/libcharon/plugins/forecast/forecast_listener.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2010-2014 Martin Willi + * Copyright (C) 2010-2014 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program 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 General Public License + * for more details. + */ + +/** + * @defgroup forecast_listener forecast_listener + * @{ @ingroup forecast + */ + +#ifndef FORECAST_LISTENER_H_ +#define FORECAST_LISTENER_H_ + +#include + +typedef struct forecast_listener_t forecast_listener_t; + +/** + * Listener to register the set of IPs we forward received multi/broadcasts to. + */ +struct forecast_listener_t { + + /** + * Implements listener_t interface. + */ + listener_t listener; + + /** + * Create an enumerator over active tunnels. + * + * The enumerator enumerates over local or remote traffic selectors, + * associated firewall marks and if decasulated packets should get + * reinjected into other tunnels. + * + * @param local TRUE to enumerate local, FALSE to enumerate remote TS + * @return enumerator over (traffic_selector_t*, u_int, bool) + */ + enumerator_t* (*create_enumerator)(forecast_listener_t *this, bool local); + + /** + * Set the broadcast address of the LAN interface. + * + * @param bcast broadcast address + */ + void (*set_broadcast)(forecast_listener_t *this, host_t *bcast); + + /** + * Destroy a forecast_listener_t. + */ + void (*destroy)(forecast_listener_t *this); +}; + +/** + * Create a forecast_listener instance. + */ +forecast_listener_t *forecast_listener_create(); + +#endif /** FORECAST_LISTENER_H_ @}*/ diff --git a/src/libcharon/plugins/forecast/forecast_plugin.c b/src/libcharon/plugins/forecast/forecast_plugin.c new file mode 100644 index 000000000..a129b76b2 --- /dev/null +++ b/src/libcharon/plugins/forecast/forecast_plugin.c @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2010-2014 Martin Willi + * Copyright (C) 2010-2014 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program 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 General Public License + * for more details. + */ + +#include "forecast_plugin.h" +#include "forecast_listener.h" +#include "forecast_forwarder.h" + +#include + +typedef struct private_forecast_plugin_t private_forecast_plugin_t; + +/** + * Private data of forecast plugin + */ +struct private_forecast_plugin_t { + + /** + * implements plugin interface + */ + forecast_plugin_t public; + + /** + * Listener registering active tunnels + */ + forecast_listener_t *listener; + + /** + * Broadcast/Multicast sniffer and forwarder + */ + forecast_forwarder_t *forwarder; +}; + +METHOD(plugin_t, get_name, char*, + private_forecast_plugin_t *this) +{ + return "forecast"; +} + +/** + * Register plugin features + */ +static bool register_forecast(private_forecast_plugin_t *this, + plugin_feature_t *feature, bool reg, void *data) +{ + if (reg) + { + this->forwarder = forecast_forwarder_create(this->listener); + if (!this->forwarder) + { + return FALSE; + } + charon->bus->add_listener(charon->bus, &this->listener->listener); + } + else + { + charon->bus->remove_listener(charon->bus, &this->listener->listener); + this->forwarder->destroy(this->forwarder); + } + return TRUE; +} + +METHOD(plugin_t, get_features, int, + private_forecast_plugin_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_CALLBACK((plugin_feature_callback_t)register_forecast, NULL), + PLUGIN_PROVIDE(CUSTOM, "forecast"), + }; + *features = f; + return countof(f); +} + +METHOD(plugin_t, destroy, void, + private_forecast_plugin_t *this) +{ + this->listener->destroy(this->listener); + free(this); +} + +/** + * Plugin constructor + */ +plugin_t *forecast_plugin_create() +{ + private_forecast_plugin_t *this; + + if (!lib->caps->keep(lib->caps, CAP_NET_RAW)) + { + DBG1(DBG_NET, "forecast plugin requires CAP_NET_RAW capability"); + return NULL; + } + + INIT(this, + .public = { + .plugin = { + .get_name = _get_name, + .get_features = _get_features, + .reload = (void*)return_false, + .destroy = _destroy, + }, + }, + .listener = forecast_listener_create(), + ); + + return &this->public.plugin; +} diff --git a/src/libcharon/plugins/forecast/forecast_plugin.h b/src/libcharon/plugins/forecast/forecast_plugin.h new file mode 100644 index 000000000..739ca4d79 --- /dev/null +++ b/src/libcharon/plugins/forecast/forecast_plugin.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010-2014 Martin Willi + * Copyright (C) 2010-2014 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program 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 General Public License + * for more details. + */ + +/** + * @defgroup forecast forecast + * @ingroup cplugins + * + * @defgroup forecast_plugin forecast_plugin + * @{ @ingroup forecast + */ + +#ifndef FORECAST_PLUGIN_H_ +#define FORECAST_PLUGIN_H_ + +#include + +typedef struct forecast_plugin_t forecast_plugin_t; + +/** + * Broadcast/Multicast forwarding plugin. + */ +struct forecast_plugin_t { + + /** + * Implements plugin interface. + */ + plugin_t plugin; +}; + +#endif /** FORECAST_PLUGIN_H_ @}*/