/* * 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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * 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); if ((status = dladm_getsnap_conf(handle, params->iptun_param_linkid, &conf)) != 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_open_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, ¶ms->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(¶ms, 0, sizeof (params)); params.iptun_param_linkid = linkid; if ((status = i_iptun_get_dbparams(handle, ¶ms)) == DLADM_STATUS_OK) status = i_iptun_create_sys(handle, ¶ms); 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)); }