| /* |
| * Copyright (c) 2013-2016 Intel Corporation. All rights reserved. |
| * |
| * This software is available to you under a choice of one of two |
| * licenses. You may choose to be licensed under the terms of the GNU |
| * General Public License (GPL) Version 2, available from the file |
| * COPYING in the main directory of this source tree, or the |
| * OpenIB.org BSD license below: |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials |
| * provided with the distribution. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| * |
| */ |
| |
| #include "iwarp_pm.h" |
| |
| static LIST_HEAD(mapped_ports); /* list of mapped ports */ |
| |
| /** |
| * create_iwpm_map_request - Create a new map request tracking object |
| * @req_nlh: netlink header of the received client message |
| * @src_addr: the local address of the client initiating the request |
| * @remote_addr: the destination (the port mapper peer) address |
| * @assochandle: unique number per host |
| * @msg_type: message types are request, accept and ack |
| * @send_msg: message to retransmit to the remote port mapper peer, |
| * if the request isn't serviced on time. |
| */ |
| iwpm_mapping_request *create_iwpm_map_request(struct nlmsghdr *req_nlh, |
| struct sockaddr_storage *src_addr, struct sockaddr_storage *remote_addr, |
| __u64 assochandle, int msg_type, iwpm_send_msg *send_msg) |
| { |
| iwpm_mapping_request *iwpm_map_req; |
| __u32 type = 0, seq = 0, pid = 0; |
| |
| /* create iwpm conversation tracking object */ |
| iwpm_map_req = malloc(sizeof(iwpm_mapping_request)); |
| if (!iwpm_map_req) |
| return NULL; |
| if (req_nlh) { |
| type = req_nlh->nlmsg_type; |
| seq = req_nlh->nlmsg_seq; |
| pid = req_nlh->nlmsg_pid; |
| } |
| memset(iwpm_map_req, 0, sizeof(iwpm_mapping_request)); |
| iwpm_map_req->timeout = IWPM_MAP_REQ_TIMEOUT; |
| iwpm_map_req->complete = 0; |
| iwpm_map_req->msg_type = msg_type; |
| iwpm_map_req->send_msg = send_msg; |
| |
| iwpm_map_req->nlmsg_type = type; |
| iwpm_map_req->nlmsg_seq = seq; |
| iwpm_map_req->nlmsg_pid = pid; |
| /* assochandle helps match iwpm request sent to remote peer with future iwpm accept/reject */ |
| iwpm_map_req->assochandle = assochandle; |
| if (!assochandle) |
| iwpm_map_req->assochandle = (uintptr_t)iwpm_map_req; |
| |
| memcpy(&iwpm_map_req->src_addr, src_addr, sizeof(struct sockaddr_storage)); |
| /* keep record of remote IP address and port */ |
| memcpy(&iwpm_map_req->remote_addr, remote_addr, sizeof(struct sockaddr_storage)); |
| return iwpm_map_req; |
| } |
| |
| /** |
| * add_iwpm_map_request - Add a map request tracking object to a global list |
| * @iwpm_map_req: mapping request to be saved |
| */ |
| void add_iwpm_map_request(iwpm_mapping_request *iwpm_map_req) |
| { |
| pthread_mutex_lock(&map_req_mutex); |
| list_add(&mapping_reqs, &iwpm_map_req->entry); |
| /* if not wake, signal the thread that a new request has been posted */ |
| if (!wake) |
| pthread_cond_signal(&cond_req_complete); |
| pthread_mutex_unlock(&map_req_mutex); |
| } |
| |
| /** |
| * remove_iwpm_map_request - Free a map request tracking object |
| * @iwpm_map_req: mapping request to be removed |
| * |
| * Routine must be called within lock context |
| */ |
| void remove_iwpm_map_request(iwpm_mapping_request *iwpm_map_req) |
| { |
| if (!iwpm_map_req->complete && iwpm_map_req->msg_type != IWARP_PM_REQ_ACK) { |
| iwpm_debug(IWARP_PM_RETRY_DBG, "remove_iwpm_map_request: " |
| "Timeout for request (type = %u pid = %d)\n", |
| iwpm_map_req->msg_type, iwpm_map_req->nlmsg_pid); |
| } |
| list_del(&iwpm_map_req->entry); |
| if (iwpm_map_req->send_msg) |
| free(iwpm_map_req->send_msg); |
| free(iwpm_map_req); |
| } |
| |
| /** |
| * update_iwpm_map_request - Find and update a map request tracking object |
| * @assochandle: the request assochandle to search for |
| * @src_addr: the request src address to search for |
| * @msg_type: the request type to search for |
| * @iwpm_copy_req: to store a copy of the found map request object |
| * @update: if set update the found request, otherwise don't update |
| */ |
| int update_iwpm_map_request(__u64 assochandle, struct sockaddr_storage *src_addr, |
| int msg_type, iwpm_mapping_request *iwpm_copy_req, int update) |
| { |
| iwpm_mapping_request *iwpm_map_req; |
| int ret = -EINVAL; |
| |
| pthread_mutex_lock(&map_req_mutex); |
| /* look for a matching entry in the list */ |
| list_for_each(&mapping_reqs, iwpm_map_req, entry) { |
| if (assochandle == iwpm_map_req->assochandle && |
| (msg_type & iwpm_map_req->msg_type) && |
| check_same_sockaddr(src_addr, &iwpm_map_req->src_addr)) { |
| ret = 0; |
| /* get a copy of the request (a different thread is in charge of freeing it) */ |
| memcpy(iwpm_copy_req, iwpm_map_req, sizeof(iwpm_mapping_request)); |
| if (!update) |
| goto update_map_request_exit; |
| if (iwpm_map_req->complete) |
| goto update_map_request_exit; |
| |
| /* update the request object */ |
| if (iwpm_map_req->msg_type == IWARP_PM_REQ_ACK) { |
| iwpm_map_req->timeout = IWPM_MAP_REQ_TIMEOUT; |
| iwpm_map_req->complete = 0; |
| } else { |
| /* already serviced request could be freed */ |
| iwpm_map_req->timeout = 0; |
| iwpm_map_req->complete = 1; |
| } |
| goto update_map_request_exit; |
| } |
| } |
| update_map_request_exit: |
| pthread_mutex_unlock(&map_req_mutex); |
| return ret; |
| } |
| |
| /** |
| * send_iwpm_msg - Form and send iwpm message to the remote peer |
| */ |
| int send_iwpm_msg(void (*form_msg_type)(iwpm_wire_msg *, iwpm_msg_parms *), |
| iwpm_msg_parms *msg_parms, struct sockaddr_storage *recv_addr, int send_sock) |
| { |
| iwpm_send_msg send_msg; |
| |
| form_msg_type(&send_msg.data, msg_parms); |
| form_iwpm_send_msg(send_sock, recv_addr, msg_parms->msize, &send_msg); |
| return add_iwpm_pending_msg(&send_msg); |
| } |
| |
| /** |
| * get_iwpm_tcp_port - Get a new TCP port from the host stack |
| * @addr_family: should be valid AF_INET or AF_INET6 |
| * @requested_port: set only if reopening of mapped port |
| * @mapped_addr: to store the mapped TCP port |
| * @new_sock: to store socket handle (bound to the mapped TCP port) |
| */ |
| static int get_iwpm_tcp_port(__u16 addr_family, __be16 requested_port, |
| struct sockaddr_storage *mapped_addr, int *new_sock) |
| { |
| sockaddr_union bind_addr; |
| struct sockaddr_in *bind_in4; |
| struct sockaddr_in6 *bind_in6; |
| socklen_t sockname_len; |
| __be16 *new_port = NULL, *mapped_port = NULL; |
| const char *str_err = ""; |
| |
| /* create a socket */ |
| *new_sock = socket(addr_family, SOCK_STREAM, 0); |
| if (*new_sock < 0) { |
| str_err = "Unable to create socket"; |
| goto get_tcp_port_error; |
| } |
| |
| memset(&bind_addr, 0, sizeof(bind_addr)); |
| switch (addr_family) { |
| case AF_INET: |
| mapped_port = &((struct sockaddr_in *)mapped_addr)->sin_port; |
| bind_in4 = &bind_addr.v4_sockaddr; |
| bind_in4->sin_family = addr_family; |
| bind_in4->sin_addr.s_addr = htobe32(INADDR_ANY); |
| if (requested_port) |
| requested_port = *mapped_port; |
| bind_in4->sin_port = requested_port; |
| new_port = &bind_in4->sin_port; |
| break; |
| case AF_INET6: |
| mapped_port = &((struct sockaddr_in6 *)mapped_addr)->sin6_port; |
| bind_in6 = &bind_addr.v6_sockaddr; |
| bind_in6->sin6_family = addr_family; |
| bind_in6->sin6_addr = in6addr_any; |
| if (requested_port) |
| requested_port = *mapped_port; |
| bind_in6->sin6_port = requested_port; |
| new_port = &bind_in6->sin6_port; |
| break; |
| default: |
| str_err = "Invalid Internet address family"; |
| goto get_tcp_port_error; |
| } |
| |
| if (bind(*new_sock, &bind_addr.sock_addr, sizeof(bind_addr))) { |
| str_err = "Unable to bind the socket"; |
| goto get_tcp_port_error; |
| } |
| /* get the TCP port */ |
| sockname_len = sizeof(bind_addr); |
| if (getsockname(*new_sock, &bind_addr.sock_addr, &sockname_len)) { |
| str_err = "Unable to get socket name"; |
| goto get_tcp_port_error; |
| } |
| *mapped_port = *new_port; |
| iwpm_debug(IWARP_PM_ALL_DBG, "get_iwpm_tcp_port: Open tcp port " |
| "(addr family = %04X, requested port = %04X, mapped port = %04X).\n", |
| addr_family, be16toh(requested_port), be16toh(*mapped_port)); |
| return 0; |
| get_tcp_port_error: |
| syslog(LOG_WARNING, "get_iwpm_tcp_port: %s (addr family = %04X, requested port = %04X).\n", |
| str_err, addr_family, be16toh(requested_port)); |
| return -errno; |
| } |
| |
| /** |
| * get_iwpm_port - Allocate and initialize a new mapped port object |
| */ |
| static iwpm_mapped_port *get_iwpm_port(int client_idx, struct sockaddr_storage *local_addr, |
| struct sockaddr_storage *mapped_addr, int sd) |
| { |
| iwpm_mapped_port *iwpm_port; |
| |
| iwpm_port = malloc(sizeof(iwpm_mapped_port)); |
| if (!iwpm_port) { |
| syslog(LOG_WARNING, "get_iwpm_port: Unable to allocate a mapped port.\n"); |
| return NULL; |
| } |
| memset(iwpm_port, 0, sizeof(*iwpm_port)); |
| |
| /* record local and mapped address in the mapped port object */ |
| memcpy(&iwpm_port->local_addr, local_addr, sizeof(struct sockaddr_storage)); |
| memcpy(&iwpm_port->mapped_addr, mapped_addr, sizeof(struct sockaddr_storage)); |
| iwpm_port->owner_client = client_idx; |
| iwpm_port->sd = sd; |
| atomic_init(&iwpm_port->ref_cnt, 1); |
| if (is_wcard_ipaddr(local_addr)) |
| iwpm_port->wcard = 1; |
| return iwpm_port; |
| } |
| |
| /** |
| * create_iwpm_mapped_port - Create a new mapped port object |
| * @local_addr: local address to be mapped (IP address and TCP port) |
| * @client_idx: the index of the client owner of the mapped port |
| */ |
| iwpm_mapped_port *create_iwpm_mapped_port(struct sockaddr_storage *local_addr, int client_idx, __u32 flags) |
| { |
| iwpm_mapped_port *iwpm_port; |
| struct sockaddr_storage mapped_addr; |
| int new_sd; |
| |
| memcpy(&mapped_addr, local_addr, sizeof(mapped_addr)); |
| /* get a tcp port from the host net stack */ |
| if (flags & IWPM_FLAGS_NO_PORT_MAP) { |
| new_sd = -1; |
| } else { |
| if (get_iwpm_tcp_port(local_addr->ss_family, 0, &mapped_addr, &new_sd)) |
| goto create_mapped_port_error; |
| } |
| iwpm_port = get_iwpm_port(client_idx, local_addr, &mapped_addr, new_sd); |
| return iwpm_port; |
| |
| create_mapped_port_error: |
| iwpm_debug(IWARP_PM_ALL_DBG, "create_iwpm_mapped_port: Could not make port mapping.\n"); |
| return NULL; |
| } |
| |
| /** |
| * reopen_iwpm_mapped_port - Create a new mapped port object |
| * @local_addr: local address to be mapped (IP address and TCP port) |
| * @mapped_addr: mapped address to be remapped (IP address and TCP port) |
| * @client_idx: the index of the client owner of the mapped port |
| */ |
| iwpm_mapped_port *reopen_iwpm_mapped_port(struct sockaddr_storage *local_addr, |
| struct sockaddr_storage *mapped_addr, int client_idx, |
| __u32 flags) |
| { |
| iwpm_mapped_port *iwpm_port; |
| int new_sd = -1; |
| const char *str_err = ""; |
| |
| if (local_addr->ss_family != mapped_addr->ss_family) { |
| str_err = "Different local and mapped sockaddr families"; |
| goto reopen_mapped_port_error; |
| } |
| if (!(flags & IWPM_FLAGS_NO_PORT_MAP)) { |
| if (get_iwpm_tcp_port(local_addr->ss_family, htobe16(1), mapped_addr, &new_sd)) |
| goto reopen_mapped_port_error; |
| } |
| iwpm_port = get_iwpm_port(client_idx, local_addr, mapped_addr, new_sd); |
| return iwpm_port; |
| |
| reopen_mapped_port_error: |
| iwpm_debug(IWARP_PM_ALL_DBG, "reopen_iwpm_mapped_port: Could not make port mapping (%s).\n", |
| str_err); |
| if (new_sd >= 0) |
| close(new_sd); |
| return NULL; |
| } |
| |
| /** |
| * add_iwpm_mapped_port - Add mapping to a global list |
| * @iwpm_port: mapping to be saved |
| */ |
| void add_iwpm_mapped_port(iwpm_mapped_port *iwpm_port) |
| { |
| static int dbg_idx = 1; |
| if (atomic_load(&iwpm_port->ref_cnt) > 1) |
| return; |
| iwpm_debug(IWARP_PM_ALL_DBG, "add_iwpm_mapped_port: Adding a new mapping #%d\n", dbg_idx++); |
| list_add(&mapped_ports, &iwpm_port->entry); |
| } |
| |
| /** |
| * check_same_sockaddr - Compare two sock addresses; |
| * return true if they are same, false otherwise |
| */ |
| int check_same_sockaddr(struct sockaddr_storage *sockaddr_a, struct sockaddr_storage *sockaddr_b) |
| { |
| int ret = 0; |
| if (sockaddr_a->ss_family == sockaddr_b->ss_family) { |
| switch (sockaddr_a->ss_family) { |
| case AF_INET: { |
| struct sockaddr_in *in4addr_a = (struct sockaddr_in *)sockaddr_a; |
| struct sockaddr_in *in4addr_b = (struct sockaddr_in *)sockaddr_b; |
| |
| if ((in4addr_a->sin_addr.s_addr == in4addr_b->sin_addr.s_addr) |
| && (in4addr_a->sin_port == in4addr_b->sin_port)) |
| ret = 1; |
| |
| break; |
| } |
| case AF_INET6: { |
| struct sockaddr_in6 *in6addr_a = (struct sockaddr_in6 *)sockaddr_a; |
| struct sockaddr_in6 *in6addr_b = (struct sockaddr_in6 *)sockaddr_b; |
| |
| if ((!memcmp(in6addr_a->sin6_addr.s6_addr, |
| in6addr_b->sin6_addr.s6_addr, IWPM_IPADDR_SIZE)) && |
| (in6addr_a->sin6_port == in6addr_b->sin6_port)) |
| ret = 1; |
| |
| break; |
| } |
| default: |
| syslog(LOG_WARNING, "check_same_sockaddr: Invalid addr family 0x%02X\n", |
| sockaddr_a->ss_family); |
| break; |
| } |
| } |
| return ret; |
| } |
| |
| /** |
| * find_iwpm_mapping - Find saved mapped port object |
| * @search_addr: IP address and port to search for in the list |
| * @not_mapped: if set, compare local addresses, otherwise compare mapped addresses |
| * |
| * Compares the search_sockaddr to the addresses in the list, |
| * to find a saved port object with the sockaddr or |
| * a wild card address with the same tcp port |
| */ |
| iwpm_mapped_port *find_iwpm_mapping(struct sockaddr_storage *search_addr, |
| int not_mapped) |
| { |
| iwpm_mapped_port *iwpm_port, *saved_iwpm_port = NULL; |
| struct sockaddr_storage *current_addr; |
| |
| list_for_each(&mapped_ports, iwpm_port, entry) { |
| current_addr = (not_mapped)? &iwpm_port->local_addr : &iwpm_port->mapped_addr; |
| |
| if (get_sockaddr_port(search_addr) == get_sockaddr_port(current_addr)) { |
| if (check_same_sockaddr(search_addr, current_addr) || |
| iwpm_port->wcard || is_wcard_ipaddr(search_addr)) { |
| saved_iwpm_port = iwpm_port; |
| goto find_mapping_exit; |
| } |
| } |
| } |
| find_mapping_exit: |
| return saved_iwpm_port; |
| } |
| |
| /** |
| * find_iwpm_same_mapping - Find saved mapped port object |
| * @search_addr: IP address and port to search for in the list |
| * @not_mapped: if set, compare local addresses, otherwise compare mapped addresses |
| * |
| * Compares the search_sockaddr to the addresses in the list, |
| * to find a saved port object with the same sockaddr |
| */ |
| iwpm_mapped_port *find_iwpm_same_mapping(struct sockaddr_storage *search_addr, |
| int not_mapped) |
| { |
| iwpm_mapped_port *iwpm_port, *saved_iwpm_port = NULL; |
| struct sockaddr_storage *current_addr; |
| |
| list_for_each(&mapped_ports, iwpm_port, entry) { |
| current_addr = (not_mapped)? &iwpm_port->local_addr : &iwpm_port->mapped_addr; |
| if (check_same_sockaddr(search_addr, current_addr)) { |
| saved_iwpm_port = iwpm_port; |
| goto find_same_mapping_exit; |
| } |
| } |
| find_same_mapping_exit: |
| return saved_iwpm_port; |
| } |
| |
| /** |
| * free_iwpm_port - Free mapping object |
| * @iwpm_port: mapped port object to be freed |
| */ |
| void free_iwpm_port(iwpm_mapped_port *iwpm_port) |
| { |
| if (iwpm_port->sd != -1) |
| close(iwpm_port->sd); |
| free(iwpm_port); |
| } |
| |
| /** |
| * remove_iwpm_mapped_port - Remove a mapping from a global list |
| * @iwpm_port: mapping to be removed |
| * |
| * Called only by the main iwarp port mapper thread |
| */ |
| void remove_iwpm_mapped_port(iwpm_mapped_port *iwpm_port) |
| { |
| static int dbg_idx = 1; |
| iwpm_debug(IWARP_PM_ALL_DBG, "remove_iwpm_mapped_port: index = %d\n", dbg_idx++); |
| |
| list_del(&iwpm_port->entry); |
| } |
| |
| void print_iwpm_mapped_ports(void) |
| { |
| iwpm_mapped_port *iwpm_port; |
| int i = 0; |
| |
| syslog(LOG_WARNING, "print_iwpm_mapped_ports:\n"); |
| |
| list_for_each(&mapped_ports, iwpm_port, entry) { |
| syslog(LOG_WARNING, "Mapping #%d\n", i++); |
| print_iwpm_sockaddr(&iwpm_port->local_addr, "Local address", IWARP_PM_DEBUG); |
| print_iwpm_sockaddr(&iwpm_port->mapped_addr, "Mapped address", IWARP_PM_DEBUG); |
| } |
| } |
| |
| /** |
| * form_iwpm_send_msg - Form a message to send on the wire |
| */ |
| void form_iwpm_send_msg(int pm_sock, struct sockaddr_storage *dest, |
| int length, iwpm_send_msg *send_msg) |
| { |
| send_msg->pm_sock = pm_sock; |
| send_msg->length = length; |
| memcpy(&send_msg->dest_addr, dest, sizeof(send_msg->dest_addr)); |
| } |
| |
| /** |
| * add_iwpm_pending_msg - Add wire message to a global list of pending messages |
| * @send_msg: message to send to the remote port mapper peer |
| */ |
| int add_iwpm_pending_msg(iwpm_send_msg *send_msg) |
| { |
| iwpm_pending_msg *pending_msg = malloc(sizeof(iwpm_pending_msg)); |
| if (!pending_msg) { |
| syslog(LOG_WARNING, "add_iwpm_pending_msg: Unable to allocate message.\n"); |
| return -ENOMEM; |
| } |
| memcpy(&pending_msg->send_msg, send_msg, sizeof(iwpm_send_msg)); |
| |
| pthread_mutex_lock(&pending_msg_mutex); |
| list_add(&pending_messages, &pending_msg->entry); |
| pthread_mutex_unlock(&pending_msg_mutex); |
| /* signal the thread that a new message has been posted */ |
| pthread_cond_signal(&cond_pending_msg); |
| return 0; |
| } |
| |
| /** |
| * free_iwpm_mapped_ports - Free all iwpm mapped port objects |
| */ |
| void free_iwpm_mapped_ports(void) |
| { |
| iwpm_mapped_port *iwpm_port; |
| |
| while ((iwpm_port = list_pop(&mapped_ports, iwpm_mapped_port, entry))) |
| free_iwpm_port(iwpm_port); |
| } |