--- Makefile.am | 2 +- src/6to4.c | 569 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/connman.h | 3 + 3 files changed, 573 insertions(+), 1 deletions(-) create mode 100644 src/6to4.c
diff --git a/Makefile.am b/Makefile.am index 144fcc5..8f13f56 100644 --- a/Makefile.am +++ b/Makefile.am @@ -77,7 +77,7 @@ src_connmand_SOURCES = $(gdbus_sources) $(gdhcp_sources) \ src/wifi.c src/storage.c src/dbus.c src/config.c \ src/technology.c src/counter.c src/location.c \ src/session.c src/tethering.c src/wpad.c src/wispr.c \ - src/stats.c src/iptables.c src/dnsproxy.c + src/stats.c src/iptables.c src/dnsproxy.c src/6to4.c src_connmand_LDADD = $(builtin_libadd) @GLIB_LIBS@ @DBUS_LIBS@ \ @CAPNG_LIBS@ @XTABLES_LIBS@ -lresolv -ldl diff --git a/src/6to4.c b/src/6to4.c new file mode 100644 index 0000000..b07eeed --- /dev/null +++ b/src/6to4.c @@ -0,0 +1,569 @@ +/* + * + * Connection Manager + * + * Copyright (C) 2011 Nokia Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <linux/ip.h> +#include <linux/if_tunnel.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <sys/ioctl.h> +#include <unistd.h> + +#include <connman.h> +#include <connman/log.h> + +static int tunnel_created; + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *) (((void *)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +#ifndef IP_DF +#define IP_DF 0x4000 /* Flag: "Don't Fragment" */ +#endif + +struct rtnl_handle { + int fd; + struct sockaddr_nl local; + struct sockaddr_nl peer; + __u32 seq; + __u32 dump; +}; + +/* Note that addattr32(), addattr_l(), rtnl_close(), rtnl_open() and + * rtnl_talk() are from libnetlink.c that is found in iproute2 package + * and copyright by Alexey Kuznetsov et al. + */ +static int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *rta; + if (NLMSG_ALIGN(n->nlmsg_len) + len > (unsigned int)maxlen) { + DBG("Error! max allowed bound %d exceeded", 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, int 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) > + (unsigned int)maxlen) { + DBG("addattr_l message exceeded bound of %d", 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; +} + +static void rtnl_close(struct rtnl_handle *rth) +{ + if (rth->fd >= 0) { + close(rth->fd); + rth->fd = -1; + } +} + +static int rtnl_open(struct rtnl_handle *rth) +{ + socklen_t addr_len; + int sndbuf = 32768; + int rcvbuf = 32768; + + memset(rth, 0, sizeof(*rth)); + + rth->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (rth->fd < 0) { + DBG("Cannot open netlink socket: %s", strerror(errno)); + return -1; + } + + if (setsockopt(rth->fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, + sizeof(sndbuf)) < 0) { + DBG("SO_SNDBUF: %s", strerror(errno)); + return -1; + } + + if (setsockopt(rth->fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, + sizeof(rcvbuf)) < 0) { + DBG("SO_RCVBUF: %s", strerror(errno)); + return -1; + } + + memset(&rth->local, 0, sizeof(rth->local)); + rth->local.nl_family = AF_NETLINK; + rth->local.nl_groups = 0; + + if (bind(rth->fd, (struct sockaddr *)&rth->local, + sizeof(rth->local)) < 0) { + DBG("Cannot bind netlink socket: %s", strerror(errno)); + return -1; + } + addr_len = sizeof(rth->local); + if (getsockname(rth->fd, (struct sockaddr *)&rth->local, + &addr_len) < 0) { + DBG("Cannot getsockname: %s", strerror(errno)); + return -1; + } + if (addr_len != sizeof(rth->local)) { + DBG("Wrong address length %d", addr_len); + return -1; + } + if (rth->local.nl_family != AF_NETLINK) { + DBG("Wrong address family %d", rth->local.nl_family); + return -1; + } + rth->seq = time(NULL); + return 0; +} + +static int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer) +{ + int status; + unsigned seq; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + struct iovec iov = { + .iov_base = (void *)n, + .iov_len = n->nlmsg_len + }; + struct msghdr msg = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + char buf[16384]; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = peer; + nladdr.nl_groups = groups; + + n->nlmsg_seq = seq = ++rtnl->seq; + + if (answer == NULL) + n->nlmsg_flags |= NLM_F_ACK; + + status = sendmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + DBG("Cannot talk to rtnetlink"); + return -1; + } + + memset(buf, 0, sizeof(buf)); + + iov.iov_base = buf; + + while (1) { + iov.iov_len = sizeof(buf); + status = recvmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + DBG("netlink receive error %s (%d)", + strerror(errno), errno); + return -1; + } + if (status == 0) { + DBG("EOF on netlink"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + DBG("sender address length == %d", msg.msg_namelen); + return -1; + } + for (h = (struct nlmsghdr *)buf; status >= (int)sizeof(*h); ) { + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l < 0 || len > status) { + if (msg.msg_flags & MSG_TRUNC) { + DBG("Truncated message"); + return -1; + } + DBG("!!!malformed message: len=%d", len); + return -1; + } + + if (nladdr.nl_pid != (unsigned)peer || + h->nlmsg_pid != rtnl->local.nl_pid || + h->nlmsg_seq != seq) { + /* Don't forget to skip that message. */ + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr *)((char *)h + + NLMSG_ALIGN(len)); + continue; + } + + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = + (struct nlmsgerr *)NLMSG_DATA(h); + if (l < (int)sizeof(struct nlmsgerr)) { + DBG("ERROR truncated"); + } else { + errno = -err->error; + if (errno == 0) { + if (answer) + memcpy(answer, h, + h->nlmsg_len); + return 0; + } + DBG("RTNETLINK answers"); + } + return -1; + } + if (answer) { + memcpy(answer, h, h->nlmsg_len); + return 0; + } + + DBG("Unexpected reply!!!"); + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + DBG("Message truncated"); + continue; + } + if (status) { + DBG("!!!Remnant of size %d", status); + return -1; + } + } +} + +static int tunnel_create(struct in_addr *addr) +{ + struct ip_tunnel_parm p; + struct ifreq ifr; + int fd = -1; + int ret; + + /* ip tunnel add tun6to4 mode sit remote any local 1.2.3.4 ttl 64 */ + + memset(&p, 0, sizeof(struct ip_tunnel_parm)); + memset(&ifr, 0, sizeof(struct ifreq)); + + p.iph.version = 4; + p.iph.ihl = 5; + p.iph.frag_off = htons(IP_DF); + p.iph.protocol = IPPROTO_IPV6; + p.iph.saddr = addr->s_addr; + p.iph.ttl = 64; + strncpy(p.name, "tun6to4", IFNAMSIZ); + + strncpy(ifr.ifr_name, "sit0", IFNAMSIZ); + ifr.ifr_ifru.ifru_data = (void *)&p; + fd = socket(AF_INET, SOCK_DGRAM, 0); + ret = ioctl(fd, SIOCADDTUNNEL, &ifr); + if (ret) + DBG("add tunnel %s failed: %s", ifr.ifr_name, strerror(errno)); + close(fd); + + return ret; +} + +static int tunnel_up() +{ + struct ifreq ifr; + int fd = -1; + __u32 mask; + __u32 flags; + int ret; + + /* ip link set dev tun6to4 mtu 1472 up */ + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + return -1; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, "tun6to4", IFNAMSIZ); + ifr.ifr_mtu = 1472; + ret = ioctl(fd, SIOCSIFMTU, &ifr); + if (ret < 0) + DBG("link set failed: %s", strerror(errno)); + if (ret) + goto done; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, "tun6to4", IFNAMSIZ); + ret = ioctl(fd, SIOCGIFFLAGS, &ifr); + if (ret) + goto done; + + mask = IFF_UP; + flags = IFF_UP; + + if ((ifr.ifr_flags ^ flags) & mask) { + ifr.ifr_flags &= ~mask; + ifr.ifr_flags |= mask & flags; + ret = ioctl(fd, SIOCSIFFLAGS, &ifr); + if (ret) + DBG("SIOCSIFFLAGS failed: %s", strerror(errno)); + } + +done: + close(fd); + return ret; +} + +static void tunnel_destroy() +{ + struct ip_tunnel_parm p; + struct ifreq ifr; + int fd = -1; + int ret; + + if (tunnel_created == 0) + return; + + /* ip tunnel del tun6to4 */ + + memset(&p, 0, sizeof(struct ip_tunnel_parm)); + memset(&ifr, 0, sizeof(struct ifreq)); + + p.iph.version = 4; + p.iph.ihl = 5; + p.iph.protocol = IPPROTO_IPV6; + strncpy(p.name, "tun6to4", IFNAMSIZ); + + strncpy(ifr.ifr_name, "tun6to4", IFNAMSIZ); + ifr.ifr_ifru.ifru_data = (void *)&p; + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + DBG("%s", strerror(errno)); + return; + } + + ret = ioctl(fd, SIOCDELTUNNEL, &ifr); + if (ret) + DBG("del tunnel %s failed: %s", ifr.ifr_name, strerror(errno)); + else + tunnel_created = 0; + + close(fd); +} + +static int tunnel_set_addr(unsigned int a, unsigned int b, + unsigned int c, unsigned int d) +{ + struct rtnl_handle rth; + struct in6_addr addr6; + char *ip6addr; + int ret; + + struct { + struct nlmsghdr n; + struct ifaddrmsg ifa; + char buf[256]; + } req; + + /* ip -6 addr add dev tun6to4 2002:0102:0304::1/64 */ + + memset(&req, 0, sizeof(req)); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL; + req.n.nlmsg_type = RTM_NEWADDR; + req.ifa.ifa_family = AF_INET6; + req.ifa.ifa_prefixlen = 64; + req.ifa.ifa_index = if_nametoindex("tun6to4"); + if (req.ifa.ifa_index == 0) { + DBG("cannot find device tun6to4"); + ret = -1; + goto done; + } + + ip6addr = g_strdup_printf("2002:%02x%02x:%02x%02x::1", a, b, c, d); + inet_pton(AF_INET6, ip6addr, &addr6); + DBG("ipv6 address %s", ip6addr); + g_free(ip6addr); + + addattr_l(&req.n, sizeof(req), IFA_LOCAL, &addr6.s6_addr, 16); + addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &addr6.s6_addr, 16); + + ret = rtnl_open(&rth); + if (ret < 0) { + DBG("rtnl open failed"); + goto done; + } + + ret = rtnl_talk(&rth, &req.n, 0, 0, NULL); + if (ret < 0) { + DBG("rtnl talk failed"); + goto done; + } + +done: + rtnl_close(&rth); + + return ret; +} + +static int tunnel_add_route() +{ + struct rtnl_handle rth; + struct in6_addr addr6; + int index; + int ret = 0; + + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + + /* ip -6 route add ::/0 via ::192.88.99.1 dev tun6to4 metric 1 */ + + index = if_nametoindex("tun6to4"); + if (index == 0) { + DBG("cannot find device tun6to4"); + return -1; + } + + memset(&req, 0, sizeof(req)); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL; + req.n.nlmsg_type = RTM_NEWROUTE; + req.r.rtm_family = AF_INET6; + req.r.rtm_table = RT_TABLE_MAIN; + req.r.rtm_protocol = RTPROT_BOOT; + req.r.rtm_scope = RT_SCOPE_UNIVERSE; + req.r.rtm_type = RTN_UNICAST; + req.r.rtm_dst_len = 0; + req.r.rtm_scope = RT_SCOPE_LINK; + + inet_pton(AF_INET6, "::192.88.99.1", &addr6); + + addattr_l(&req.n, sizeof(req), RTA_GATEWAY, &addr6.s6_addr, 16); + addattr32(&req.n, sizeof(req), RTA_OIF, index); + addattr32(&req.n, sizeof(req), RTA_PRIORITY, 1); + + ret = rtnl_open(&rth); + if (ret < 0) { + DBG("rtnl open failed"); + goto done; + } + + ret = rtnl_talk(&rth, &req.n, 0, 0, NULL); + if (ret < 0) { + DBG("rtnl talk failed"); + goto done; + } + +done: + rtnl_close(&rth); + + return ret; +} + +int __connman_6to4_probe(const char *address) +{ + struct in_addr ip4addr; + in_addr_t addr; + unsigned int a, b, c, d; + int ret; + + DBG("address %s", address); + + if (tunnel_created) + return 0; + + if (address == NULL) + return -1; + + if (inet_aton(address, &ip4addr) == 0) + return -1; + + addr = ntohl(ip4addr.s_addr); + + a = (addr & 0xff000000) >> 24; + b = (addr & 0x00ff0000) >> 16; + c = (addr & 0x0000ff00) >> 8; + d = addr & 0x000000ff; + + /* 6to4 tunnel is only usable if we have a public IPv4 address */ + if (a == 10 || (a == 192 && b == 168) || + (a == 172 && (b >= 16 && b <= 31))) + return -1; + + ret = tunnel_create(&ip4addr); + if (ret) + return -1; + + tunnel_created = 1; + + ret = tunnel_up(); + if (ret) + goto error; + + ret = tunnel_set_addr(a, b, c, d); + if (ret) + goto error; + + ret = tunnel_add_route(); + if (ret) + goto error; + + return 0; + +error: + tunnel_destroy(); + return -1; +} + +void __connman_6to4_remove() +{ + DBG(""); + + if (tunnel_created) + tunnel_destroy(); +} diff --git a/src/connman.h b/src/connman.h index e320014..a8ef170 100644 --- a/src/connman.h +++ b/src/connman.h @@ -629,3 +629,6 @@ void __connman_dnsproxy_cleanup(void); int __connman_dnsproxy_append(const char *interface, const char *domain, const char *server); int __connman_dnsproxy_remove(const char *interface, const char *domain, const char *server); void __connman_dnsproxy_flush(void); + +int __connman_6to4_probe(const char *address); +void __connman_6to4_remove(); -- 1.7.0.4 _______________________________________________ connman mailing list connman@connman.net http://lists.connman.net/listinfo/connman