hostapd/wpa_supplicant/ctrl_iface_udp.c
Janusz Dziedzic 4db216fcf7 wpa_supplicant: Add support for IPv6 with UDP ctrl_iface
Add IPv6 support when using udp/udp-remote control interface using the
following new build configuration options:

CONFIG_CTRL_IFACE=udp6
CONFIG_CTRL_IFACE=udp6-remote

This is useful for testing, while we don't need to assign IPv4 address
(static or using DHCP) and can just use auto configured IPv6 addresses
(link local, which is based on the MAC address). Also add scope id
support for link local case.

For example,
./wpa_cli
./wpa_cli -i ::1,9877
./wpa_cli -i fe80::203:7fff:fe05:69%wlan0,9877

Signed-off-by: Janusz Dziedzic <janusz.dziedzic@tieto.com>
2014-02-25 16:43:01 +02:00

685 lines
17 KiB
C

/*
* WPA Supplicant / UDP socket -based control interface
* Copyright (c) 2004-2005, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "includes.h"
#include "common.h"
#include "eloop.h"
#include "config.h"
#include "eapol_supp/eapol_supp_sm.h"
#include "wpa_supplicant_i.h"
#include "ctrl_iface.h"
#include "common/wpa_ctrl.h"
#define COOKIE_LEN 8
/* Per-interface ctrl_iface */
/**
* struct wpa_ctrl_dst - Internal data structure of control interface monitors
*
* This structure is used to store information about registered control
* interface monitors into struct wpa_supplicant. This data is private to
* ctrl_iface_udp.c and should not be touched directly from other files.
*/
struct wpa_ctrl_dst {
struct wpa_ctrl_dst *next;
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
struct sockaddr_in6 addr;
#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */
struct sockaddr_in addr;
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
socklen_t addrlen;
int debug_level;
int errors;
};
struct ctrl_iface_priv {
struct wpa_supplicant *wpa_s;
int sock;
struct wpa_ctrl_dst *ctrl_dst;
u8 cookie[COOKIE_LEN];
};
static void wpa_supplicant_ctrl_iface_send(struct ctrl_iface_priv *priv,
int level, const char *buf,
size_t len);
static int wpa_supplicant_ctrl_iface_attach(struct ctrl_iface_priv *priv,
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
struct sockaddr_in6 *from,
#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */
struct sockaddr_in *from,
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
socklen_t fromlen)
{
struct wpa_ctrl_dst *dst;
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
char addr[INET6_ADDRSTRLEN];
#endif /* CONFIG_UDP_IPV6 */
dst = os_zalloc(sizeof(*dst));
if (dst == NULL)
return -1;
os_memcpy(&dst->addr, from, sizeof(*from));
dst->addrlen = fromlen;
dst->debug_level = MSG_INFO;
dst->next = priv->ctrl_dst;
priv->ctrl_dst = dst;
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor attached %s:%d",
inet_ntop(AF_INET6, &from->sin6_addr, addr, sizeof(*from)),
ntohs(from->sin6_port));
#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */
wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor attached %s:%d",
inet_ntoa(from->sin_addr), ntohs(from->sin_port));
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
return 0;
}
static int wpa_supplicant_ctrl_iface_detach(struct ctrl_iface_priv *priv,
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
struct sockaddr_in6 *from,
#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */
struct sockaddr_in *from,
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
socklen_t fromlen)
{
struct wpa_ctrl_dst *dst, *prev = NULL;
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
char addr[INET6_ADDRSTRLEN];
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
dst = priv->ctrl_dst;
while (dst) {
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
if (from->sin6_port == dst->addr.sin6_port &&
!os_memcmp(&from->sin6_addr, &dst->addr.sin6_addr,
sizeof(from->sin6_addr))) {
wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor detached %s:%d",
inet_ntop(AF_INET6, &from->sin6_addr, addr,
sizeof(*from)),
ntohs(from->sin6_port));
#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */
if (from->sin_addr.s_addr == dst->addr.sin_addr.s_addr &&
from->sin_port == dst->addr.sin_port) {
wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor detached "
"%s:%d", inet_ntoa(from->sin_addr),
ntohs(from->sin_port));
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
if (prev == NULL)
priv->ctrl_dst = dst->next;
else
prev->next = dst->next;
os_free(dst);
return 0;
}
prev = dst;
dst = dst->next;
}
return -1;
}
static int wpa_supplicant_ctrl_iface_level(struct ctrl_iface_priv *priv,
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
struct sockaddr_in6 *from,
#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */
struct sockaddr_in *from,
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
socklen_t fromlen,
char *level)
{
struct wpa_ctrl_dst *dst;
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
char addr[INET6_ADDRSTRLEN];
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
wpa_printf(MSG_DEBUG, "CTRL_IFACE LEVEL %s", level);
dst = priv->ctrl_dst;
while (dst) {
#if CONFIG_CTRL_IFACE_UDP_IPV6
if (from->sin6_port == dst->addr.sin6_port &&
!os_memcmp(&from->sin6_addr, &dst->addr.sin6_addr,
sizeof(from->sin6_addr))) {
wpa_printf(MSG_DEBUG, "CTRL_IFACE changed monitor level %s:%d",
inet_ntop(AF_INET6, &from->sin6_addr, addr,
sizeof(*from)),
ntohs(from->sin6_port));
#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */
if (from->sin_addr.s_addr == dst->addr.sin_addr.s_addr &&
from->sin_port == dst->addr.sin_port) {
wpa_printf(MSG_DEBUG, "CTRL_IFACE changed monitor "
"level %s:%d", inet_ntoa(from->sin_addr),
ntohs(from->sin_port));
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
dst->debug_level = atoi(level);
return 0;
}
dst = dst->next;
}
return -1;
}
static char *
wpa_supplicant_ctrl_iface_get_cookie(struct ctrl_iface_priv *priv,
size_t *reply_len)
{
char *reply;
reply = os_malloc(7 + 2 * COOKIE_LEN + 1);
if (reply == NULL) {
*reply_len = 1;
return NULL;
}
os_memcpy(reply, "COOKIE=", 7);
wpa_snprintf_hex(reply + 7, 2 * COOKIE_LEN + 1,
priv->cookie, COOKIE_LEN);
*reply_len = 7 + 2 * COOKIE_LEN;
return reply;
}
static void wpa_supplicant_ctrl_iface_receive(int sock, void *eloop_ctx,
void *sock_ctx)
{
struct wpa_supplicant *wpa_s = eloop_ctx;
struct ctrl_iface_priv *priv = sock_ctx;
char buf[256], *pos;
int res;
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
struct sockaddr_in6 from;
#ifndef CONFIG_CTRL_IFACE_UDP_REMOTE
char addr[INET6_ADDRSTRLEN];
#endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */
#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */
struct sockaddr_in from;
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
socklen_t fromlen = sizeof(from);
char *reply = NULL;
size_t reply_len = 0;
int new_attached = 0;
u8 cookie[COOKIE_LEN];
res = recvfrom(sock, buf, sizeof(buf) - 1, 0,
(struct sockaddr *) &from, &fromlen);
if (res < 0) {
perror("recvfrom(ctrl_iface)");
return;
}
#ifndef CONFIG_CTRL_IFACE_UDP_REMOTE
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
inet_ntop(AF_INET6, &from.sin6_addr, addr, sizeof(from));
if (os_strcmp(addr, "::1")) {
wpa_printf(MSG_DEBUG, "CTRL: Drop packet from unexpected source %s",
addr);
}
#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */
if (from.sin_addr.s_addr != htonl((127 << 24) | 1)) {
/*
* The OS networking stack is expected to drop this kind of
* frames since the socket is bound to only localhost address.
* Just in case, drop the frame if it is coming from any other
* address.
*/
wpa_printf(MSG_DEBUG, "CTRL: Drop packet from unexpected "
"source %s", inet_ntoa(from.sin_addr));
return;
}
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
#endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */
buf[res] = '\0';
if (os_strcmp(buf, "GET_COOKIE") == 0) {
reply = wpa_supplicant_ctrl_iface_get_cookie(priv, &reply_len);
goto done;
}
/*
* Require that the client includes a prefix with the 'cookie' value
* fetched with GET_COOKIE command. This is used to verify that the
* client has access to a bidirectional link over UDP in order to
* avoid attacks using forged localhost IP address even if the OS does
* not block such frames from remote destinations.
*/
if (os_strncmp(buf, "COOKIE=", 7) != 0) {
wpa_printf(MSG_DEBUG, "CTLR: No cookie in the request - "
"drop request");
return;
}
if (hexstr2bin(buf + 7, cookie, COOKIE_LEN) < 0) {
wpa_printf(MSG_DEBUG, "CTLR: Invalid cookie format in the "
"request - drop request");
return;
}
if (os_memcmp(cookie, priv->cookie, COOKIE_LEN) != 0) {
wpa_printf(MSG_DEBUG, "CTLR: Invalid cookie in the request - "
"drop request");
return;
}
pos = buf + 7 + 2 * COOKIE_LEN;
while (*pos == ' ')
pos++;
if (os_strcmp(pos, "ATTACH") == 0) {
if (wpa_supplicant_ctrl_iface_attach(priv, &from, fromlen))
reply_len = 1;
else {
new_attached = 1;
reply_len = 2;
}
} else if (os_strcmp(pos, "DETACH") == 0) {
if (wpa_supplicant_ctrl_iface_detach(priv, &from, fromlen))
reply_len = 1;
else
reply_len = 2;
} else if (os_strncmp(pos, "LEVEL ", 6) == 0) {
if (wpa_supplicant_ctrl_iface_level(priv, &from, fromlen,
pos + 6))
reply_len = 1;
else
reply_len = 2;
} else {
reply = wpa_supplicant_ctrl_iface_process(wpa_s, pos,
&reply_len);
}
done:
if (reply) {
sendto(sock, reply, reply_len, 0, (struct sockaddr *) &from,
fromlen);
os_free(reply);
} else if (reply_len == 1) {
sendto(sock, "FAIL\n", 5, 0, (struct sockaddr *) &from,
fromlen);
} else if (reply_len == 2) {
sendto(sock, "OK\n", 3, 0, (struct sockaddr *) &from,
fromlen);
}
if (new_attached)
eapol_sm_notify_ctrl_attached(wpa_s->eapol);
}
static void wpa_supplicant_ctrl_iface_msg_cb(void *ctx, int level, int global,
const char *txt, size_t len)
{
struct wpa_supplicant *wpa_s = ctx;
if (wpa_s == NULL || wpa_s->ctrl_iface == NULL)
return;
wpa_supplicant_ctrl_iface_send(wpa_s->ctrl_iface, level, txt, len);
}
struct ctrl_iface_priv *
wpa_supplicant_ctrl_iface_init(struct wpa_supplicant *wpa_s)
{
struct ctrl_iface_priv *priv;
int port = WPA_CTRL_IFACE_PORT;
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
struct sockaddr_in6 addr;
int domain = PF_INET6;
#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */
struct sockaddr_in addr;
int domain = PF_INET;
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
priv = os_zalloc(sizeof(*priv));
if (priv == NULL)
return NULL;
priv->wpa_s = wpa_s;
priv->sock = -1;
os_get_random(priv->cookie, COOKIE_LEN);
if (wpa_s->conf->ctrl_interface == NULL)
return priv;
priv->sock = socket(domain, SOCK_DGRAM, 0);
if (priv->sock < 0) {
perror("socket(PF_INET)");
goto fail;
}
os_memset(&addr, 0, sizeof(addr));
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
addr.sin6_family = AF_INET6;
#ifdef CONFIG_CTRL_IFACE_UDP_REMOTE
addr.sin6_addr = in6addr_any;
#else /* CONFIG_CTRL_IFACE_UDP_REMOTE */
inet_pton(AF_INET6, "::1", &addr.sin6_addr);
#endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */
#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */
addr.sin_family = AF_INET;
#ifdef CONFIG_CTRL_IFACE_UDP_REMOTE
addr.sin_addr.s_addr = INADDR_ANY;
#else /* CONFIG_CTRL_IFACE_UDP_REMOTE */
addr.sin_addr.s_addr = htonl((127 << 24) | 1);
#endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
try_again:
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
addr.sin6_port = htons(port);
#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */
addr.sin_port = htons(port);
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
if (bind(priv->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
port--;
if ((WPA_CTRL_IFACE_PORT - port) < WPA_CTRL_IFACE_PORT_LIMIT)
goto try_again;
perror("bind(AF_INET)");
goto fail;
}
#ifdef CONFIG_CTRL_IFACE_UDP_REMOTE
wpa_msg(wpa_s, MSG_DEBUG, "ctrl_iface_init UDP port: %d", port);
#endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */
eloop_register_read_sock(priv->sock, wpa_supplicant_ctrl_iface_receive,
wpa_s, priv);
wpa_msg_register_cb(wpa_supplicant_ctrl_iface_msg_cb);
return priv;
fail:
if (priv->sock >= 0)
close(priv->sock);
os_free(priv);
return NULL;
}
void wpa_supplicant_ctrl_iface_deinit(struct ctrl_iface_priv *priv)
{
struct wpa_ctrl_dst *dst, *prev;
if (priv->sock > -1) {
eloop_unregister_read_sock(priv->sock);
if (priv->ctrl_dst) {
/*
* Wait before closing the control socket if
* there are any attached monitors in order to allow
* them to receive any pending messages.
*/
wpa_printf(MSG_DEBUG, "CTRL_IFACE wait for attached "
"monitors to receive messages");
os_sleep(0, 100000);
}
close(priv->sock);
priv->sock = -1;
}
dst = priv->ctrl_dst;
while (dst) {
prev = dst;
dst = dst->next;
os_free(prev);
}
os_free(priv);
}
static void wpa_supplicant_ctrl_iface_send(struct ctrl_iface_priv *priv,
int level, const char *buf,
size_t len)
{
struct wpa_ctrl_dst *dst, *next;
char levelstr[10];
int idx;
char *sbuf;
int llen;
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
char addr[INET6_ADDRSTRLEN];
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
dst = priv->ctrl_dst;
if (priv->sock < 0 || dst == NULL)
return;
os_snprintf(levelstr, sizeof(levelstr), "<%d>", level);
llen = os_strlen(levelstr);
sbuf = os_malloc(llen + len);
if (sbuf == NULL)
return;
os_memcpy(sbuf, levelstr, llen);
os_memcpy(sbuf + llen, buf, len);
idx = 0;
while (dst) {
next = dst->next;
if (level >= dst->debug_level) {
#ifdef CONFIG_CTRL_IFACE_UDP_IPV6
wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor send %s:%d",
inet_ntop(AF_INET6, &dst->addr.sin6_addr,
addr, sizeof(dst->addr)),
ntohs(dst->addr.sin6_port));
#else /* CONFIG_CTRL_IFACE_UDP_IPV6 */
wpa_printf(MSG_DEBUG, "CTRL_IFACE monitor send %s:%d",
inet_ntoa(dst->addr.sin_addr),
ntohs(dst->addr.sin_port));
#endif /* CONFIG_CTRL_IFACE_UDP_IPV6 */
if (sendto(priv->sock, sbuf, llen + len, 0,
(struct sockaddr *) &dst->addr,
sizeof(dst->addr)) < 0) {
perror("sendto(CTRL_IFACE monitor)");
dst->errors++;
if (dst->errors > 10) {
wpa_supplicant_ctrl_iface_detach(
priv, &dst->addr,
dst->addrlen);
}
} else
dst->errors = 0;
}
idx++;
dst = next;
}
os_free(sbuf);
}
void wpa_supplicant_ctrl_iface_wait(struct ctrl_iface_priv *priv)
{
wpa_printf(MSG_DEBUG, "CTRL_IFACE - %s - wait for monitor",
priv->wpa_s->ifname);
eloop_wait_for_read_sock(priv->sock);
}
/* Global ctrl_iface */
struct ctrl_iface_global_priv {
int sock;
u8 cookie[COOKIE_LEN];
};
static char *
wpa_supplicant_global_get_cookie(struct ctrl_iface_global_priv *priv,
size_t *reply_len)
{
char *reply;
reply = os_malloc(7 + 2 * COOKIE_LEN + 1);
if (reply == NULL) {
*reply_len = 1;
return NULL;
}
os_memcpy(reply, "COOKIE=", 7);
wpa_snprintf_hex(reply + 7, 2 * COOKIE_LEN + 1,
priv->cookie, COOKIE_LEN);
*reply_len = 7 + 2 * COOKIE_LEN;
return reply;
}
static void wpa_supplicant_global_ctrl_iface_receive(int sock, void *eloop_ctx,
void *sock_ctx)
{
struct wpa_global *global = eloop_ctx;
struct ctrl_iface_global_priv *priv = sock_ctx;
char buf[256], *pos;
int res;
struct sockaddr_in from;
socklen_t fromlen = sizeof(from);
char *reply;
size_t reply_len;
u8 cookie[COOKIE_LEN];
res = recvfrom(sock, buf, sizeof(buf) - 1, 0,
(struct sockaddr *) &from, &fromlen);
if (res < 0) {
perror("recvfrom(ctrl_iface)");
return;
}
#ifndef CONFIG_CTRL_IFACE_UDP_REMOTE
if (from.sin_addr.s_addr != htonl((127 << 24) | 1)) {
/*
* The OS networking stack is expected to drop this kind of
* frames since the socket is bound to only localhost address.
* Just in case, drop the frame if it is coming from any other
* address.
*/
wpa_printf(MSG_DEBUG, "CTRL: Drop packet from unexpected "
"source %s", inet_ntoa(from.sin_addr));
return;
}
#endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */
buf[res] = '\0';
if (os_strcmp(buf, "GET_COOKIE") == 0) {
reply = wpa_supplicant_global_get_cookie(priv, &reply_len);
goto done;
}
if (os_strncmp(buf, "COOKIE=", 7) != 0) {
wpa_printf(MSG_DEBUG, "CTLR: No cookie in the request - "
"drop request");
return;
}
if (hexstr2bin(buf + 7, cookie, COOKIE_LEN) < 0) {
wpa_printf(MSG_DEBUG, "CTLR: Invalid cookie format in the "
"request - drop request");
return;
}
if (os_memcmp(cookie, priv->cookie, COOKIE_LEN) != 0) {
wpa_printf(MSG_DEBUG, "CTLR: Invalid cookie in the request - "
"drop request");
return;
}
pos = buf + 7 + 2 * COOKIE_LEN;
while (*pos == ' ')
pos++;
reply = wpa_supplicant_global_ctrl_iface_process(global, pos,
&reply_len);
done:
if (reply) {
sendto(sock, reply, reply_len, 0, (struct sockaddr *) &from,
fromlen);
os_free(reply);
} else if (reply_len) {
sendto(sock, "FAIL\n", 5, 0, (struct sockaddr *) &from,
fromlen);
}
}
struct ctrl_iface_global_priv *
wpa_supplicant_global_ctrl_iface_init(struct wpa_global *global)
{
struct ctrl_iface_global_priv *priv;
struct sockaddr_in addr;
int port = WPA_GLOBAL_CTRL_IFACE_PORT;
priv = os_zalloc(sizeof(*priv));
if (priv == NULL)
return NULL;
priv->sock = -1;
os_get_random(priv->cookie, COOKIE_LEN);
if (global->params.ctrl_interface == NULL)
return priv;
wpa_printf(MSG_DEBUG, "Global control interface '%s'",
global->params.ctrl_interface);
priv->sock = socket(PF_INET, SOCK_DGRAM, 0);
if (priv->sock < 0) {
perror("socket(PF_INET)");
goto fail;
}
os_memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
#ifdef CONFIG_CTRL_IFACE_UDP_REMOTE
addr.sin_addr.s_addr = INADDR_ANY;
#else /* CONFIG_CTRL_IFACE_UDP_REMOTE */
addr.sin_addr.s_addr = htonl((127 << 24) | 1);
#endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */
try_again:
addr.sin_port = htons(port);
if (bind(priv->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
port++;
if ((port - WPA_GLOBAL_CTRL_IFACE_PORT) <
WPA_GLOBAL_CTRL_IFACE_PORT_LIMIT)
goto try_again;
perror("bind(AF_INET)");
goto fail;
}
#ifdef CONFIG_CTRL_IFACE_UDP_REMOTE
wpa_printf(MSG_DEBUG, "global_ctrl_iface_init UDP port: %d", port);
#endif /* CONFIG_CTRL_IFACE_UDP_REMOTE */
eloop_register_read_sock(priv->sock,
wpa_supplicant_global_ctrl_iface_receive,
global, priv);
return priv;
fail:
if (priv->sock >= 0)
close(priv->sock);
os_free(priv);
return NULL;
}
void
wpa_supplicant_global_ctrl_iface_deinit(struct ctrl_iface_global_priv *priv)
{
if (priv->sock >= 0) {
eloop_unregister_read_sock(priv->sock);
close(priv->sock);
}
os_free(priv);
}