OpenSolaris_b135/lib/libdladm/common/libdliptun.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <assert.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stropts.h>
#include <string.h>
#include <netdb.h>
#include <sys/conf.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <inet/iptun.h>
#include <sys/dls.h>
#include <libdlpi.h>
#include <libdladm_impl.h>
#include <libdllink.h>
#include <libdliptun.h>

/*
 * IP Tunneling Administration Library.
 * This library is used by dladm(1M) and to configure IP tunnel links.
 */

#define	IPTUN_CONF_TYPE		"type"
#define	IPTUN_CONF_LADDR	"laddr"
#define	IPTUN_CONF_RADDR	"raddr"

/*
 * If IPTUN_CREATE and IPTUN_MODIFY include IPsec policy and IPsec hasn't
 * loaded yet, the ioctls may return EAGAIN.  We try the ioctl
 * IPTUN_IOCTL_ATTEMPT_LIMIT times and wait IPTUN_IOCTL_ATTEMPT_INTERVAL
 * microseconds between attempts.
 */
#define	IPTUN_IOCTL_ATTEMPT_LIMIT	3
#define	IPTUN_IOCTL_ATTEMPT_INTERVAL	10000

dladm_status_t
i_iptun_ioctl(dladm_handle_t handle, int cmd, void *dp)
{
	dladm_status_t	status = DLADM_STATUS_OK;
	uint_t		attempt;

	for (attempt = 0; attempt < IPTUN_IOCTL_ATTEMPT_LIMIT; attempt++) {
		if (attempt != 0)
			(void) usleep(IPTUN_IOCTL_ATTEMPT_INTERVAL);
		status = (ioctl(dladm_dld_fd(handle), cmd, dp) == 0) ?
		    DLADM_STATUS_OK : dladm_errno2status(errno);
		if (status != DLADM_STATUS_TRYAGAIN)
			break;
	}
	return (status);
}

/*
 * Given tunnel paramaters as supplied by a library consumer, fill in kernel
 * parameters to be passed down to the iptun control device.
 */
static dladm_status_t
i_iptun_kparams(dladm_handle_t handle, const iptun_params_t *params,
    iptun_kparams_t *ik)
{
	dladm_status_t	status;
	struct addrinfo	*ai, hints;
	iptun_kparams_t	tmpik;
	iptun_type_t	iptuntype = IPTUN_TYPE_UNKNOWN;

	(void) memset(ik, 0, sizeof (*ik));

	ik->iptun_kparam_linkid = params->iptun_param_linkid;

	if (params->iptun_param_flags & IPTUN_PARAM_TYPE) {
		ik->iptun_kparam_type = iptuntype = params->iptun_param_type;
		ik->iptun_kparam_flags |= IPTUN_KPARAM_TYPE;
	}

	if (params->iptun_param_flags & (IPTUN_PARAM_LADDR|IPTUN_PARAM_RADDR)) {
		if (iptuntype == IPTUN_TYPE_UNKNOWN) {
			/*
			 * We need to get the type of this existing tunnel in
			 * order to validate and/or look up the right kind of
			 * IP address.
			 */
			tmpik.iptun_kparam_linkid = params->iptun_param_linkid;
			status = i_iptun_ioctl(handle, IPTUN_INFO, &tmpik);
			if (status != DLADM_STATUS_OK)
				return (status);
			iptuntype = tmpik.iptun_kparam_type;
		}

		(void) memset(&hints, 0, sizeof (hints));
		switch (iptuntype) {
		case IPTUN_TYPE_IPV4:
		case IPTUN_TYPE_6TO4:
			hints.ai_family = AF_INET;
			break;
		case IPTUN_TYPE_IPV6:
			hints.ai_family = AF_INET6;
			break;
		}
	}

	if (params->iptun_param_flags & IPTUN_PARAM_LADDR) {
		if (getaddrinfo(params->iptun_param_laddr, NULL, &hints, &ai) !=
		    0)
			return (DLADM_STATUS_BADIPTUNLADDR);
		if (ai->ai_next != NULL) {
			freeaddrinfo(ai);
			return (DLADM_STATUS_BADIPTUNLADDR);
		}
		(void) memcpy(&ik->iptun_kparam_laddr, ai->ai_addr,
		    ai->ai_addrlen);
		ik->iptun_kparam_flags |= IPTUN_KPARAM_LADDR;
		freeaddrinfo(ai);
	}

	if (params->iptun_param_flags & IPTUN_PARAM_RADDR) {
		if (getaddrinfo(params->iptun_param_raddr, NULL, &hints, &ai) !=
		    0)
			return (DLADM_STATUS_BADIPTUNRADDR);
		if (ai->ai_next != NULL) {
			freeaddrinfo(ai);
			return (DLADM_STATUS_BADIPTUNRADDR);
		}
		(void) memcpy(&ik->iptun_kparam_raddr, ai->ai_addr,
		    ai->ai_addrlen);
		ik->iptun_kparam_flags |= IPTUN_KPARAM_RADDR;
		freeaddrinfo(ai);
	}

	if (params->iptun_param_flags & IPTUN_PARAM_SECINFO) {
		ik->iptun_kparam_secinfo = params->iptun_param_secinfo;
		ik->iptun_kparam_flags |= IPTUN_KPARAM_SECINFO;
	}

	return (DLADM_STATUS_OK);
}

/*
 * The inverse of i_iptun_kparams().  Given kernel tunnel paramaters as
 * returned from an IPTUN_INFO ioctl, fill in tunnel parameters.
 */
static dladm_status_t
i_iptun_params(const iptun_kparams_t *ik, iptun_params_t *params)
{
	socklen_t salen;

	(void) memset(params, 0, sizeof (*params));

	params->iptun_param_linkid = ik->iptun_kparam_linkid;

	if (ik->iptun_kparam_flags & IPTUN_KPARAM_TYPE) {
		params->iptun_param_type = ik->iptun_kparam_type;
		params->iptun_param_flags |= IPTUN_PARAM_TYPE;
	}

	if (ik->iptun_kparam_flags & IPTUN_KPARAM_LADDR) {
		salen = ik->iptun_kparam_laddr.ss_family == AF_INET ?
		    sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6);
		if (getnameinfo((const struct sockaddr *)
		    &ik->iptun_kparam_laddr, salen, params->iptun_param_laddr,
		    sizeof (params->iptun_param_laddr), NULL, 0,
		    NI_NUMERICHOST) != 0) {
			return (DLADM_STATUS_BADIPTUNLADDR);
		}
		params->iptun_param_flags |= IPTUN_PARAM_LADDR;
	}

	if (ik->iptun_kparam_flags & IPTUN_KPARAM_RADDR) {
		salen = ik->iptun_kparam_raddr.ss_family == AF_INET ?
		    sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6);
		if (getnameinfo((const struct sockaddr *)
		    &ik->iptun_kparam_raddr, salen, params->iptun_param_raddr,
		    sizeof (params->iptun_param_raddr), NULL, 0,
		    NI_NUMERICHOST) != 0) {
			return (DLADM_STATUS_BADIPTUNRADDR);
		}
		params->iptun_param_flags |= IPTUN_PARAM_RADDR;
	}

	if (ik->iptun_kparam_flags & IPTUN_KPARAM_SECINFO) {
		params->iptun_param_secinfo = ik->iptun_kparam_secinfo;
		params->iptun_param_flags |= IPTUN_PARAM_SECINFO;
	}

	if (ik->iptun_kparam_flags & IPTUN_KPARAM_IMPLICIT)
		params->iptun_param_flags |= IPTUN_PARAM_IMPLICIT;

	if (ik->iptun_kparam_flags & IPTUN_KPARAM_IPSECPOL)
		params->iptun_param_flags |= IPTUN_PARAM_IPSECPOL;

	return (DLADM_STATUS_OK);
}

dladm_status_t
i_iptun_get_sysparams(dladm_handle_t handle, iptun_params_t *params)
{
	dladm_status_t	status = DLADM_STATUS_OK;
	iptun_kparams_t	ik;

	ik.iptun_kparam_linkid = params->iptun_param_linkid;
	status = i_iptun_ioctl(handle, IPTUN_INFO, &ik);
	if (status == DLADM_STATUS_OK)
		status = i_iptun_params(&ik, params);
	return (status);
}

/*
 * Read tunnel parameters from persistent storage.  Note that the tunnel type
 * is the only thing which must always be in the configuratioh.  All other
 * parameters (currently the source and destination addresses) may or may not
 * have been configured, and therefore may not have been set.
 */
static dladm_status_t
i_iptun_get_dbparams(dladm_handle_t handle, iptun_params_t *params)
{
	dladm_status_t		status;
	dladm_conf_t		conf;
	datalink_class_t	class;
	uint64_t		temp;

	/* First, make sure that this is an IP tunnel. */
	if ((status = dladm_datalink_id2info(handle, params->iptun_param_linkid,
	    NULL, &class, NULL, NULL, 0)) != DLADM_STATUS_OK)
		return (status);
	if (class != DATALINK_CLASS_IPTUN)
		return (DLADM_STATUS_LINKINVAL);

	status = dladm_read_conf(handle, params->iptun_param_linkid, &conf);
	if (status != DLADM_STATUS_OK)
		return (status);

	params->iptun_param_flags = 0;

	if ((status = dladm_get_conf_field(handle, conf, IPTUN_CONF_TYPE, &temp,
	    sizeof (temp))) != DLADM_STATUS_OK)
		goto done;
	params->iptun_param_type = (iptun_type_t)temp;
	params->iptun_param_flags |= IPTUN_PARAM_TYPE;

	if (dladm_get_conf_field(handle, conf, IPTUN_CONF_LADDR,
	    params->iptun_param_laddr, sizeof (params->iptun_param_laddr)) ==
	    DLADM_STATUS_OK)
		params->iptun_param_flags |= IPTUN_PARAM_LADDR;

	if (dladm_get_conf_field(handle, conf, IPTUN_CONF_RADDR,
	    params->iptun_param_raddr, sizeof (params->iptun_param_raddr)) ==
	    DLADM_STATUS_OK)
		params->iptun_param_flags |= IPTUN_PARAM_RADDR;

done:
	dladm_destroy_conf(handle, conf);
	return (status);
}

static dladm_status_t
i_iptun_create_sys(dladm_handle_t handle, iptun_params_t *params)
{
	iptun_kparams_t	ik;
	dladm_status_t	status = DLADM_STATUS_OK;

	/* The tunnel type is required for creation. */
	if (!(params->iptun_param_flags & IPTUN_PARAM_TYPE))
		return (DLADM_STATUS_IPTUNTYPEREQD);

	if ((status = i_iptun_kparams(handle, params, &ik)) == DLADM_STATUS_OK)
		status = i_iptun_ioctl(handle, IPTUN_CREATE, &ik);
	return (status);
}

static dladm_status_t
i_iptun_create_db(dladm_handle_t handle, const char *name,
    iptun_params_t *params, uint32_t media)
{
	dladm_conf_t	conf;
	dladm_status_t	status;
	uint64_t	storage;

	status = dladm_create_conf(handle, name, params->iptun_param_linkid,
	    DATALINK_CLASS_IPTUN, media, &conf);
	if (status != DLADM_STATUS_OK)
		return (status);

	assert(params->iptun_param_flags & IPTUN_PARAM_TYPE);
	storage = params->iptun_param_type;
	status = dladm_set_conf_field(handle, conf, IPTUN_CONF_TYPE,
	    DLADM_TYPE_UINT64, &storage);
	if (status != DLADM_STATUS_OK)
		goto done;

	if (params->iptun_param_flags & IPTUN_PARAM_LADDR) {
		status = dladm_set_conf_field(handle, conf, IPTUN_CONF_LADDR,
		    DLADM_TYPE_STR, params->iptun_param_laddr);
		if (status != DLADM_STATUS_OK)
			goto done;
	}

	if (params->iptun_param_flags & IPTUN_PARAM_RADDR) {
		status = dladm_set_conf_field(handle, conf, IPTUN_CONF_RADDR,
		    DLADM_TYPE_STR, params->iptun_param_raddr);
		if (status != DLADM_STATUS_OK)
			goto done;
	}

	status = dladm_write_conf(handle, conf);

done:
	dladm_destroy_conf(handle, conf);
	return (status);
}

static dladm_status_t
i_iptun_delete_sys(dladm_handle_t handle, datalink_id_t linkid)
{
	dladm_status_t status;

	status = i_iptun_ioctl(handle, IPTUN_DELETE, &linkid);
	if (status != DLADM_STATUS_OK)
		return (status);
	(void) dladm_destroy_datalink_id(handle, linkid, DLADM_OPT_ACTIVE);
	return (DLADM_STATUS_OK);
}

static dladm_status_t
i_iptun_modify_sys(dladm_handle_t handle, const iptun_params_t *params)
{
	iptun_kparams_t	ik;
	dladm_status_t	status;

	if ((status = i_iptun_kparams(handle, params, &ik)) == DLADM_STATUS_OK)
		status = i_iptun_ioctl(handle, IPTUN_MODIFY, &ik);
	return (status);
}

static dladm_status_t
i_iptun_modify_db(dladm_handle_t handle, const iptun_params_t *params)
{
	dladm_conf_t	conf;
	dladm_status_t	status;

	assert(params->iptun_param_flags &
	    (IPTUN_PARAM_LADDR|IPTUN_PARAM_RADDR));

	/*
	 * The only parameters that can be modified persistently are the local
	 * and remote addresses.
	 */
	if (params->iptun_param_flags & ~(IPTUN_PARAM_LADDR|IPTUN_PARAM_RADDR))
		return (DLADM_STATUS_BADARG);

	status = dladm_read_conf(handle, params->iptun_param_linkid, &conf);
	if (status != DLADM_STATUS_OK)
		return (status);

	if (params->iptun_param_flags & IPTUN_PARAM_LADDR) {
		status = dladm_set_conf_field(handle, conf, IPTUN_CONF_LADDR,
		    DLADM_TYPE_STR, (void *)params->iptun_param_laddr);
		if (status != DLADM_STATUS_OK)
			goto done;
	}

	if (params->iptun_param_flags & IPTUN_PARAM_RADDR) {
		status = dladm_set_conf_field(handle, conf, IPTUN_CONF_RADDR,
		    DLADM_TYPE_STR, (void *)params->iptun_param_raddr);
		if (status != DLADM_STATUS_OK)
			goto done;
	}

	status = dladm_write_conf(handle, conf);

done:
	dladm_destroy_conf(handle, conf);
	return (status);
}

dladm_status_t
dladm_iptun_create(dladm_handle_t handle, const char *name,
    iptun_params_t *params, uint32_t flags)
{
	dladm_status_t	status;
	uint32_t	linkmgmt_flags = flags;
	uint32_t	media;

	if (!(params->iptun_param_flags & IPTUN_PARAM_TYPE))
		return (DLADM_STATUS_IPTUNTYPEREQD);

	switch (params->iptun_param_type) {
	case IPTUN_TYPE_IPV4:
		media = DL_IPV4;
		break;
	case IPTUN_TYPE_IPV6:
		media = DL_IPV6;
		break;
	case IPTUN_TYPE_6TO4:
		media = DL_6TO4;
		break;
	default:
		return (DLADM_STATUS_IPTUNTYPE);
	}

	status = dladm_create_datalink_id(handle, name, DATALINK_CLASS_IPTUN,
	    media, linkmgmt_flags, &params->iptun_param_linkid);
	if (status != DLADM_STATUS_OK)
		return (status);

	if (flags & DLADM_OPT_PERSIST) {
		status = i_iptun_create_db(handle, name, params, media);
		if (status != DLADM_STATUS_OK)
			goto done;
	}

	if (flags & DLADM_OPT_ACTIVE) {
		status = i_iptun_create_sys(handle, params);
		if (status != DLADM_STATUS_OK && (flags & DLADM_OPT_PERSIST)) {
			(void) dladm_remove_conf(handle,
			    params->iptun_param_linkid);
		}
	}

done:
	if (status != DLADM_STATUS_OK) {
		(void) dladm_destroy_datalink_id(handle,
		    params->iptun_param_linkid, flags);
	}
	return (status);
}

dladm_status_t
dladm_iptun_delete(dladm_handle_t handle, datalink_id_t linkid, uint32_t flags)
{
	dladm_status_t		status;
	datalink_class_t	class;

	/* First, make sure that this is an IP tunnel. */
	if ((status = dladm_datalink_id2info(handle, linkid, NULL, &class, NULL,
	    NULL, 0)) != DLADM_STATUS_OK)
		return (status);
	if (class != DATALINK_CLASS_IPTUN)
		return (DLADM_STATUS_LINKINVAL);

	if (flags & DLADM_OPT_ACTIVE) {
		/*
		 * Note that if i_iptun_delete_sys() fails with
		 * DLADM_STATUS_NOTFOUND and the caller also wishes to delete
		 * the persistent configuration, we still fall through to the
		 * DLADM_OPT_PERSIST case in case the tunnel only exists
		 * persistently.
		 */
		status = i_iptun_delete_sys(handle, linkid);
		if (status != DLADM_STATUS_OK &&
		    (status != DLADM_STATUS_NOTFOUND ||
		    !(flags & DLADM_OPT_PERSIST)))
			return (status);
	}

	if (flags & DLADM_OPT_PERSIST) {
		(void) dladm_remove_conf(handle, linkid);
		(void) dladm_destroy_datalink_id(handle, linkid,
		    DLADM_OPT_PERSIST);
	}
	return (DLADM_STATUS_OK);
}

dladm_status_t
dladm_iptun_modify(dladm_handle_t handle, const iptun_params_t *params,
    uint32_t flags)
{
	dladm_status_t	status = DLADM_STATUS_OK;
	iptun_params_t	old_params;

	/*
	 * We can only modify the tunnel source, tunnel destination, or IPsec
	 * policy.
	 */
	if (!(params->iptun_param_flags &
	    (IPTUN_PARAM_LADDR|IPTUN_PARAM_RADDR|IPTUN_PARAM_SECINFO)))
		return (DLADM_STATUS_BADARG);

	if (flags & DLADM_OPT_PERSIST) {
		/*
		 * Before we change the database, save the old configuration
		 * so that we can revert back if an error occurs.
		 */
		old_params.iptun_param_linkid = params->iptun_param_linkid;
		status = i_iptun_get_dbparams(handle, &old_params);
		if (status != DLADM_STATUS_OK)
			return (status);
		/* we'll only need to revert the parameters being modified */
		old_params.iptun_param_flags = params->iptun_param_flags;

		status = i_iptun_modify_db(handle, params);
		if (status != DLADM_STATUS_OK)
			return (status);
	}

	if (flags & DLADM_OPT_ACTIVE) {
		status = i_iptun_modify_sys(handle, params);
		if (status != DLADM_STATUS_OK && (flags & DLADM_OPT_PERSIST)) {
			(void) i_iptun_modify_db(handle, &old_params);
		}
	}

	return (status);
}

dladm_status_t
dladm_iptun_getparams(dladm_handle_t handle, iptun_params_t *params,
    uint32_t flags)
{
	if (flags == DLADM_OPT_ACTIVE)
		return (i_iptun_get_sysparams(handle, params));
	else if (flags == DLADM_OPT_PERSIST)
		return (i_iptun_get_dbparams(handle, params));
	else
		return (DLADM_STATUS_BADARG);
}

static int
i_iptun_up(dladm_handle_t handle, datalink_id_t linkid, void *arg)
{
	dladm_status_t	*statusp = arg;
	dladm_status_t	status;
	iptun_params_t	params;
	boolean_t	id_up = B_FALSE;

	status = dladm_up_datalink_id(handle, linkid);
	if (status != DLADM_STATUS_OK)
		goto done;
	id_up = B_TRUE;

	(void) memset(&params, 0, sizeof (params));

	params.iptun_param_linkid = linkid;
	if ((status = i_iptun_get_dbparams(handle, &params)) == DLADM_STATUS_OK)
		status = i_iptun_create_sys(handle, &params);
done:
	if (statusp != NULL)
		*statusp = status;
	if (status != DLADM_STATUS_OK && id_up) {
		(void) dladm_destroy_datalink_id(handle, linkid,
		    DLADM_OPT_ACTIVE);
	}
	return (DLADM_WALK_CONTINUE);
}

static int
i_iptun_down(dladm_handle_t handle, datalink_id_t linkid, void *arg)
{
	dladm_status_t	*statusp = arg;
	dladm_status_t	status;

	status = i_iptun_delete_sys(handle, linkid);
	if (statusp != NULL)
		*statusp = status;
	return (DLADM_WALK_CONTINUE);
}

/* ARGSUSED */
dladm_status_t
dladm_iptun_up(dladm_handle_t handle, datalink_id_t linkid)
{
	dladm_status_t status = DLADM_STATUS_OK;

	if (linkid == DATALINK_ALL_LINKID) {
		(void) dladm_walk_datalink_id(i_iptun_up, handle, NULL,
		    DATALINK_CLASS_IPTUN, DATALINK_ANY_MEDIATYPE,
		    DLADM_OPT_PERSIST);
	} else {
		(void) i_iptun_up(handle, linkid, &status);
	}
	return (status);
}

dladm_status_t
dladm_iptun_down(dladm_handle_t handle, datalink_id_t linkid)
{
	dladm_status_t status = DLADM_STATUS_OK;

	if (linkid == DATALINK_ALL_LINKID) {
		(void) dladm_walk_datalink_id(i_iptun_down, handle, NULL,
		    DATALINK_CLASS_IPTUN, DATALINK_ANY_MEDIATYPE,
		    DLADM_OPT_ACTIVE);
	} else {
		(void) i_iptun_down(handle, linkid, &status);
	}
	return (status);
}

dladm_status_t
dladm_iptun_set6to4relay(dladm_handle_t handle, struct in_addr *relay)
{
	return (i_iptun_ioctl(handle, IPTUN_SET_6TO4RELAY, relay));
}

dladm_status_t
dladm_iptun_get6to4relay(dladm_handle_t handle, struct in_addr *relay)
{
	return (i_iptun_ioctl(handle, IPTUN_GET_6TO4RELAY, relay));
}