/*
 * Copyright (c) 2001-2022 NVIDIA CORPORATION & AFFILIATES. 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
 * 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.
 */


/*
 * system includes
 */
#if HAVE_CONFIG_H
#  include <config.h>
#endif /* HAVE_CONFIG_H */


#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fnmatch.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
#include <vlogger/vlogger.h>

/*
 * VMA specific includes
 */
#include "libvma.h"

// debugging macros
#define MODULE_NAME "match:"

#define match_logpanic             __log_panic
#define match_logerr               __log_err
#define match_logwarn              __log_warn
#define match_loginfo              __log_info
#define match_logdbg               __log_dbg
#define match_logfunc              __log_func
#define match_logfuncall           __log_funcall

/* --------------------------------------------------------------------- */
/* library static and global variables                                   */
/* --------------------------------------------------------------------- */
extern char *program_invocation_name, *program_invocation_short_name;

static void free_dbl_lst(struct dbl_lst *dbl_lst)
{
	struct dbl_lst_node *node, *tmp;

	node = dbl_lst->head;
	while (node) {
		tmp = node->next;
		if (node->data)
			free(node->data);

		free(node);
		node = tmp;
	}
	dbl_lst->head = NULL;
	dbl_lst->tail = NULL;
}

static void free_instance_content(struct instance *instance)
{
	if (!instance)
		return;

	/* free srever's rules */
	free_dbl_lst(&instance->tcp_srv_rules_lst);

	/*free client's rules */
	free_dbl_lst(&instance->tcp_clt_rules_lst);

	/* free the instance id content*/
	if (instance->id.prog_name_expr)
		free(instance->id.prog_name_expr);

	if (instance->id.user_defined_id)
		free(instance->id.user_defined_id);
	free(instance);
}

void __vma_free_resources(void)
{
	struct dbl_lst_node *node, *tmp;

	/* free the instances */
	node = __instance_list.head;
	while (node) {
		tmp = node->next;
		free_instance_content((struct instance *)node->data);
		free(node);
		node = tmp;
	}
	__instance_list.head = NULL;
	__instance_list.tail = NULL;
}

void get_address_port_rule_str(char *addr_buf, char *ports_buf, struct address_port_rule *rule)
{
	char str_addr[INET_ADDRSTRLEN];

	/* TODO: handle IPv6 in rule */
	if (rule->match_by_addr) {
		inet_ntop(AF_INET, &(rule->ipv4), str_addr, sizeof(str_addr));
		if (rule->prefixlen != 32) {
			sprintf(addr_buf, "%s/%d", str_addr, rule->prefixlen );
		} else {
			sprintf(addr_buf, "%s", str_addr);
		}
	} else {
		sprintf(addr_buf, "%s" ,"*");
	}

	if (rule->match_by_port)
		if (rule->eport > rule->sport)
			sprintf(ports_buf, "%u-%u", rule->sport, rule->eport);
		else
			sprintf(ports_buf, "%u", rule->sport);
	else
		sprintf(ports_buf, "*");
}

static void get_rule_str(struct use_family_rule *rule, char *buf, size_t len)
{
	if (!rule) {
		snprintf(buf, len, " ");
		return;
	}

	char addr_buf_first[MAX_ADDR_STR_LEN];
	char ports_buf_first[17];
	char addr_buf_second[MAX_ADDR_STR_LEN];
	char ports_buf_second[17];
	const char *target = __vma_get_transport_str(rule->target_transport);
	const char *protocol = __vma_get_protocol_str(rule->protocol);

	get_address_port_rule_str(addr_buf_first, ports_buf_first, &(rule->first));
	if (rule->use_second) {
		get_address_port_rule_str(addr_buf_second, ports_buf_second, &(rule->second));
		snprintf(buf, len, "use %s %s %s:%s:%s:%s", target, protocol, addr_buf_first, ports_buf_first, addr_buf_second, ports_buf_second);
	} else {
		snprintf(buf, len, "use %s %s %s:%s", target, protocol, addr_buf_first, ports_buf_first);
	}
}

static void get_instance_id_str(struct instance *instance, char *buf, size_t len)
{
	if (instance)
		snprintf(buf, len, "application-id %s %s", instance->id.prog_name_expr,  instance->id.user_defined_id);
	else
		snprintf(buf, len, " ");
}

static void  print_rule(struct use_family_rule *rule)
{
	char rule_str[MAX_CONF_FILE_ENTRY_STR_LEN] = " ";

	if(rule) {
		get_rule_str(rule, rule_str, MAX_CONF_FILE_ENTRY_STR_LEN);
	}
	match_logdbg("\t\t\t%s", rule_str);
}

static void  print_instance_id_str(struct instance *instance)
{
	char instance_str[MAX_CONF_FILE_ENTRY_STR_LEN] = " ";

	if(instance) {
		get_instance_id_str(instance, instance_str, MAX_CONF_FILE_ENTRY_STR_LEN);
	}
	match_logdbg("%s:", instance_str);
}

static void print_rules_lst(struct dbl_lst_node *curr_node)
{
	while (curr_node) {
		struct use_family_rule *rule = (struct use_family_rule *)curr_node->data;
		print_rule(rule);
		curr_node = curr_node->next;
	}
}

static void print_instance_conf(struct instance *instance)
{
	if (!instance) {
		match_logdbg("\tinstance is empty");
	} else {
		print_instance_id_str(instance);

		struct dbl_lst_node *node = instance->tcp_srv_rules_lst.head;
		match_logdbg("\ttcp_server's rules:");
		print_rules_lst(node);

		node = instance->tcp_clt_rules_lst.head;
		match_logdbg("\ttcp_clinet's rules:");
		print_rules_lst(node);

		node = instance->udp_rcv_rules_lst.head;
		match_logdbg("\tudp receiver rules:");
		print_rules_lst(node);

		node = instance->udp_snd_rules_lst.head;
		match_logdbg("\tudp sender rules:");
		print_rules_lst(node);

		node = instance->udp_con_rules_lst.head;
		match_logdbg("\tudp connect rules:");
		print_rules_lst(node);

		match_logdbg(" ");
	}
}

void __vma_print_conf_file(struct dbl_lst conf_lst)
{
	struct dbl_lst_node *node = conf_lst.head;

	match_logdbg("Configuration File:");
	while (node) {
		struct instance *instance = (struct instance *)node->data;
		print_instance_conf(instance);
		node = node->next;
	}
}

/* return 0 if the addresses match */
static inline int match_ipv4_addr(struct address_port_rule *rule, const struct sockaddr_in *sin)
{
	// Added netmask on rule side to avoid user mistake when configuring ip rule: 1.1.1.x/24 instead of 1.1.1.0/24
	match_logdbg("rule ip address:%d.%d.%d.%d, socket ip address:%d.%d.%d.%d ", NIPQUAD(rule->ipv4.s_addr & htonl(VMA_NETMASK(rule->prefixlen))), NIPQUAD(sin->sin_addr.s_addr & htonl(VMA_NETMASK(rule->prefixlen))));
	return ( (rule->ipv4.s_addr & htonl(VMA_NETMASK(rule->prefixlen))) != (sin->sin_addr.s_addr & htonl(VMA_NETMASK(rule->prefixlen))));
}

static int match_ip_addr_and_port(transport_t my_transport, struct use_family_rule *rule, const struct sockaddr *addr_in_first, const socklen_t addrlen_first, const struct sockaddr *addr_in_second = NULL, const socklen_t addrlen_second = 0)
{
	const struct sockaddr_in *sin_first = ( const struct sockaddr_in * )addr_in_first;
	const struct sockaddr_in *sin_second = ( const struct sockaddr_in * )addr_in_second;
	const struct sockaddr_in6 *sin6_first = ( const struct sockaddr_in6 * )addr_in_first;
	const struct sockaddr_in6 *sin6_second = ( const struct sockaddr_in6 * )addr_in_second;
	struct sockaddr_in tmp_sin_first;
	struct sockaddr_in tmp_sin_second;
	unsigned short port_first;
	unsigned short port_second;
	int match = 1;
	char addr_buf_first[MAX_ADDR_STR_LEN];
	const char *addr_str_first;
	char addr_buf_second[MAX_ADDR_STR_LEN];
	const char *addr_str_second;
	char rule_str[512];

	if ( g_vlogger_level >= VLOG_DEBUG ){

		get_rule_str(rule, rule_str, sizeof(rule_str));

		if ( sin6_first->sin6_family == AF_INET6 ) {
			addr_str_first = inet_ntop( AF_INET6, (void *)&(sin6_first->sin6_addr), addr_buf_first, MAX_ADDR_STR_LEN);
			port_first = ntohs(sin6_first->sin6_port);
		} else {
			addr_str_first = inet_ntop( AF_INET, (void *)&(sin_first->sin_addr), addr_buf_first, MAX_ADDR_STR_LEN);
			port_first = ntohs(sin_first->sin_port);
		}
		if (addr_str_first == NULL)
			addr_str_first = "INVALID_ADDR";

		if (addr_in_second) {
			if ( sin6_second->sin6_family == AF_INET6 ) {
				addr_str_second = inet_ntop( AF_INET6, (void *)&(sin6_second->sin6_addr), addr_buf_second, MAX_ADDR_STR_LEN);
				port_second = ntohs(sin6_second->sin6_port);
			} else {
				addr_str_second = inet_ntop( AF_INET, (void *)&(sin_second->sin_addr), addr_buf_second, MAX_ADDR_STR_LEN);
				port_second = ntohs(sin_second->sin_port);
			}
			if (addr_str_second == NULL)
				addr_str_second = "INVALID_ADDR";

			match_logdbg("MATCH: matching %s:%d:%s:%d to %s => ", addr_str_first, port_first, addr_str_second, port_second, rule_str);

		} else {
			match_logdbg("MATCH: matching %s:%d to %s => ", addr_str_first, port_first, rule_str);
		}

	}

	/* We currently only support IPv4 and IPv4 embedded in IPv6 */
	if ( rule->first.match_by_port ) {
		if ( sin6_first->sin6_family == AF_INET6 )
			port_first = ntohs( sin6_first->sin6_port );
		else
			port_first = ntohs( sin_first->sin_port );

		if ((port_first < rule->first.sport) || (port_first > rule->first.eport)) {
			match_logdbg("NEGATIVE MATCH by port range" );
			match = 0;
		}
	}

	if ( match && rule->first.match_by_addr ) {
		if ( __vma_sockaddr_to_vma( addr_in_first, addrlen_first, &tmp_sin_first, NULL ) ||
				match_ipv4_addr(&(rule->first), &tmp_sin_first)) {
			match_logdbg("NEGATIVE MATCH by address" );
			match = 0;
		}
	}

	if (match && rule->use_second && addr_in_second) {
		if ( rule->second.match_by_port ) {
			if ( sin6_second->sin6_family == AF_INET6 )
				port_second = ntohs( sin6_second->sin6_port );
			else
				port_second = ntohs( sin_second->sin_port );

			if ((port_second < rule->second.sport) || (port_second > rule->second.eport)) {
				match_logdbg("NEGATIVE MATCH by port range" );
				match = 0;
			}
		}

		if ( match && rule->second.match_by_addr ) {
			if ( __vma_sockaddr_to_vma( addr_in_second, addrlen_second, &tmp_sin_second, NULL ) ||
					match_ipv4_addr(&(rule->second), &tmp_sin_second)) {
				match_logdbg("NEGATIVE MATCH by address" );
				match = 0;
			}
		}
	}

	if (match) {
		if (!(rule->target_transport == TRANS_OS || rule->target_transport == TRANS_ULP || rule->target_transport == my_transport)) {
			match_logdbg("NEGATIVE MATCH by transport" );
			match = 0;
		}
		else {
			match_logdbg("POSITIVE MATCH");
		}
	}

	return match;
}

/* return 1 on match */
int __vma_match_program_name(struct instance *instance)
{
	if (!instance)
		return 1;

	return !fnmatch( instance->id.prog_name_expr, program_invocation_short_name, 0);
}

/* return 1 on match */
int __vma_match_user_defined_id(struct instance *instance, const char *app_id)
{
	int ret_val = 0;

	if (!instance || !instance->id.user_defined_id || !app_id )
		ret_val = 1;
	else if (!strcmp(app_id, "*"))
		ret_val = 1;
	else if (!strcmp(instance->id.user_defined_id, "*"))
		ret_val = 1;
	else
		ret_val = !strcmp(app_id, instance->id.user_defined_id);

	return ret_val;
}

static transport_t get_family_by_first_matching_rule(transport_t my_transport, struct dbl_lst rules_lst, const struct sockaddr *sin_first, const socklen_t addrlen_first, const struct sockaddr *sin_second = NULL, const socklen_t addrlen_second = 0)
{
	struct dbl_lst_node *node;

	for (node = rules_lst.head; node != NULL; node = node->next) {
		/* first rule wins */
		struct use_family_rule *rule = (struct use_family_rule *)node->data;
		if (rule)
			if (match_ip_addr_and_port(my_transport, rule, sin_first, addrlen_first, sin_second, addrlen_second))
				return rule->target_transport;
	}

	match_logdbg("No matching rule. Using VMA (default)" );
	return TRANS_VMA; //No matching rule or no rule at all. Don't continue to next application-id
}

static transport_t get_family_by_instance_first_matching_rule(transport_t my_transport, role_t role, const char *app_id, const struct sockaddr *sin_first, const socklen_t addrlen_first, const struct sockaddr *sin_second = NULL, const socklen_t addrlen_second = 0)
{
	transport_t target_family = TRANS_DEFAULT;

	/* if we do not have any rules we use vma */
	if ( __vma_config_empty()){
		target_family = TRANS_VMA;
	}
	else{
		struct dbl_lst_node *curr = __instance_list.head;

		while (curr && target_family == TRANS_DEFAULT) {
			struct instance *curr_instance = (struct instance *)curr->data;
			if (curr_instance) {
				/* skip if not our program */
				if (__vma_match_program_name(curr_instance) && __vma_match_user_defined_id(curr_instance, app_id)) {
					match_logdbg("MATCHING program name: %s, application-id: %s",curr_instance->id.prog_name_expr, curr_instance->id.user_defined_id);
					switch (role) {
						case ROLE_TCP_SERVER:
							target_family =	get_family_by_first_matching_rule(my_transport, curr_instance->tcp_srv_rules_lst, sin_first, addrlen_first);
							break;
						case ROLE_TCP_CLIENT:
							target_family =	get_family_by_first_matching_rule(my_transport, curr_instance->tcp_clt_rules_lst, sin_first, addrlen_first, sin_second, addrlen_second);
							break;
						case ROLE_UDP_SENDER:
							target_family =	get_family_by_first_matching_rule(my_transport, curr_instance->udp_snd_rules_lst, sin_first, addrlen_first);
							break;
						case ROLE_UDP_RECEIVER:
							target_family =	get_family_by_first_matching_rule(my_transport, curr_instance->udp_rcv_rules_lst, sin_first, addrlen_first);
							break;
						case ROLE_UDP_CONNECT:
							target_family =	get_family_by_first_matching_rule(my_transport, curr_instance->udp_con_rules_lst, sin_first, addrlen_first, sin_second, addrlen_second);
							break;
						BULLSEYE_EXCLUDE_BLOCK_START
						default:
							break;
						BULLSEYE_EXCLUDE_BLOCK_END
					}
				}
			}
			curr = curr->next;
		}
		if(!curr && target_family == TRANS_DEFAULT) {
			target_family = TRANS_VMA;
		}
	}
	return target_family;
}

/* return the result of the first matching rule found */
transport_t __vma_match_tcp_server(transport_t my_transport, const char *app_id, const struct sockaddr * sin, const socklen_t addrlen)
{
	transport_t target_family;

	target_family = get_family_by_instance_first_matching_rule(my_transport, ROLE_TCP_SERVER, app_id, sin, addrlen);

	match_logdbg("MATCH TCP SERVER (LISTEN): => %s", __vma_get_transport_str(target_family));

	return target_family;
}

transport_t __vma_match_tcp_client(transport_t my_transport, const char *app_id, const struct sockaddr * sin_first, const socklen_t addrlen_first, const struct sockaddr * sin_second, const socklen_t addrlen_second)
{
	transport_t target_family;

	target_family = get_family_by_instance_first_matching_rule(my_transport, ROLE_TCP_CLIENT, app_id, sin_first, addrlen_first, sin_second, addrlen_second);

	match_logdbg("MATCH TCP CLIENT (CONNECT): => %s", __vma_get_transport_str(target_family));

	return target_family;
}

/* return the result of the first matching rule found */
transport_t __vma_match_udp_sender(transport_t my_transport, const char *app_id, const struct sockaddr * sin, const socklen_t addrlen)
{
	transport_t target_family;

	target_family = get_family_by_instance_first_matching_rule(my_transport, ROLE_UDP_SENDER, app_id, sin, addrlen);

	match_logdbg("MATCH UDP SENDER: => %s", __vma_get_transport_str(target_family));

	return target_family;
}

transport_t __vma_match_udp_receiver(transport_t my_transport, const char *app_id, const struct sockaddr * sin, const socklen_t addrlen)
{
	transport_t target_family;

	target_family = get_family_by_instance_first_matching_rule(my_transport, ROLE_UDP_RECEIVER, app_id, sin, addrlen);

	match_logdbg("MATCH UDP RECEIVER: => %s", __vma_get_transport_str(target_family));

	return target_family;
}

transport_t __vma_match_udp_connect(transport_t my_transport, const char *app_id, const struct sockaddr * sin_first, const socklen_t addrlen_first, const struct sockaddr * sin_second, const socklen_t addrlen_second)
{
	transport_t target_family;

	target_family = get_family_by_instance_first_matching_rule(my_transport, ROLE_UDP_CONNECT, app_id, sin_first, addrlen_first, sin_second, addrlen_second);

	match_logdbg("MATCH UDP CONNECT: => %s", __vma_get_transport_str(target_family));

	return target_family;
}

/* given a set of rules see if there is a global match for current program */
static transport_t match_by_all_rules_program(in_protocol_t my_protocol, struct dbl_lst rules_lst)
{
	int any_vma = 0;
	int any_os = 0;
	int any_sdp = 0;
	transport_t target_family = TRANS_DEFAULT;
	struct dbl_lst_node *node;
	struct use_family_rule *rule;

	for (node = rules_lst.head; (node != NULL) && (target_family == TRANS_DEFAULT) ; node = node->next ) {
		/*
		 * to declare a dont care we either have a dont care address and port
		 * or the previous non global rules use the same target family as the
		 * global rule
		 */
		rule = (struct use_family_rule *)node->data;

		if (!rule)
			continue;
		if ((rule->protocol == my_protocol || my_protocol == PROTO_ALL) &&
				(rule->first.match_by_addr || rule->first.match_by_port || (rule->use_second && (rule->second.match_by_addr || rule->second.match_by_port )))) {
			/* not a global match rule - just track the target family */
			if (rule->target_transport == TRANS_VMA || rule->target_transport == TRANS_ULP)
				any_vma++;
			else if (rule->target_transport == TRANS_OS)
				any_os++;
		} else if (rule->protocol == my_protocol && !(rule->first.match_by_addr || rule->first.match_by_port || (rule->use_second && (rule->second.match_by_addr || rule->second.match_by_port )))){
			/* a global match so we can declare a match by program */
			if ((rule->target_transport == TRANS_VMA || rule->target_transport == TRANS_ULP) && (any_os == 0))
				target_family = TRANS_VMA;
			else if ((rule->target_transport == TRANS_OS) && (any_vma == 0) && (any_sdp == 0))
				target_family = TRANS_OS;
		}
	}
	if (target_family == TRANS_DEFAULT) {// no matching rules under application-id. use VMA. Don't continue to next application-id
		target_family = TRANS_VMA;
	}
	return target_family;
}

/* return tcp or vma if the port and role are don't cares */
transport_t __vma_match_by_program(in_protocol_t my_protocol, const char *app_id)
{
	transport_t server_target_family = TRANS_DEFAULT;
	transport_t client_target_family = TRANS_DEFAULT;
	transport_t target_family = TRANS_DEFAULT;
	bool b_found_app_id_match = false;

	if ( __vma_config_empty() ){
		match_logdbg("Configuration file is empty. Using VMA (default)" );
		target_family = TRANS_VMA;
	}
	else{
		struct dbl_lst_node *node = __instance_list.head;

		while (node && target_family == TRANS_DEFAULT) {
			/* need to try both server and client rules */
			struct instance* instance;
			instance = (struct instance *)node->data;
			if (instance && __vma_match_program_name(instance) && __vma_match_user_defined_id(instance, app_id)) {
				b_found_app_id_match = true;
				if (my_protocol == PROTO_TCP)
				{
					/* TCP */
					server_target_family =
							match_by_all_rules_program(my_protocol, instance->tcp_srv_rules_lst);
					client_target_family =
							match_by_all_rules_program(my_protocol, instance->tcp_clt_rules_lst);
				}
				else if(my_protocol == PROTO_UDP){
					/* UDP */
					server_target_family =
							match_by_all_rules_program(my_protocol, instance->udp_rcv_rules_lst);
					client_target_family =
							match_by_all_rules_program(my_protocol, instance->udp_snd_rules_lst);
				}

				/* only if both agree */
				if (server_target_family == client_target_family)
					target_family = server_target_family;
			}
			node = node->next;
		}
	}

	if (strcmp("VMA_DEFAULT_APPLICATION_ID", app_id) && !b_found_app_id_match)
		match_logwarn("requested VMA_APPLICATION_ID does not exist in the configuration file");
	
	return target_family;
}

/* is_ipv4_embedded_in_ipv6 -- return 1 if the given ipv6 address is ipv4   */
static int is_ipv4_embedded_in_ipv6(const struct sockaddr_in6 *sin6)
{
        static struct in6_addr ipv4_embedded_addr = {{{0}}};

        /* 10 first bytes must be 0 */
        if (memcmp(&ipv4_embedded_addr.s6_addr[0], &sin6->sin6_addr.s6_addr[0], 10))
                return 0;

        /* next two must be all zeros or all ones */
        if (((sin6->sin6_addr.s6_addr[10] == 0) &&
                 (sin6->sin6_addr.s6_addr[11] == 0)) ||
                ((sin6->sin6_addr.s6_addr[10] == 0xff) &&
                 (sin6->sin6_addr.s6_addr[11] == 0xff)))
                return 1;

        return 0;
}

#define IPV6_ADDR_IN_MIN_LEN 24
int __vma_sockaddr_to_vma(const struct sockaddr *addr_in, socklen_t addrlen, struct sockaddr_in *addr_out, int *was_ipv6)
{
	const struct sockaddr_in *sin = (const struct sockaddr_in *) addr_in;
	const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *) addr_in;
	char buf[MAX_ADDR_STR_LEN];

	/* currently VMA supports only IPv4 ... */
	if (!addr_in) {
		match_logdbg("Error __vma_sockaddr_to_vma: "
				  "provided NULL input pointer");
		errno = EINVAL;
		return -1;
	}
	if (!addr_out) {
		match_logdbg("Error __vma_sockaddr_to_vma: "
				  "provided NULL output pointer");
		errno = EINVAL;
		return -1;
	}

	if (sin->sin_family == AF_INET) {
		match_logdbg("__vma_sockaddr_to_vma: Given IPv4");
		if (addrlen < sizeof(struct sockaddr_in)) {
			match_logdbg("Error __vma_sockaddr_to_vma: "
					  "provided address length:%u < IPv4 length %d",
					  (unsigned)addrlen, (int)sizeof(struct sockaddr_in));
			errno = EINVAL;
			return -1;
		}

	memcpy(addr_out, sin, sizeof(*addr_out));
	if (was_ipv6)
		*was_ipv6 = 0;
	} else if (sin6->sin6_family == AF_INET6) {
		if (addrlen < IPV6_ADDR_IN_MIN_LEN) {
			match_logdbg("Error __vma_sockaddr_to_vma: "
					  "provided address length:%d < IPv6 length %d",
					  addrlen, IPV6_ADDR_IN_MIN_LEN);
			errno = EINVAL;
			return -1;
		}

		/* cannot convert IPv6 that is not IPv4 embedding */
		if (!is_ipv4_embedded_in_ipv6(sin6)) {
			match_logdbg("Error __vma_sockaddr_to_vma: "
					  "Given IPv6 address not an embedded IPv4");
			errno = EINVAL;
			return -1;
		}
		memset(addr_out, 0, sizeof(*addr_out));
		memcpy(&addr_out->sin_addr, &(sin6->sin6_addr.s6_addr[12]), 4);

		if (addr_out->sin_addr.s_addr == ntohl(1)) {
			addr_out->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
			match_logdbg("__vma_sockaddr_to_vma: Given IPv6 loopback address");
		} else
			match_logdbg("__vma_sockaddr_to_vma: Given IPv4 embedded in IPv6");

		addr_out->sin_family = AF_INET;
		addr_out->sin_port = sin6->sin6_port;


		if (inet_ntop (addr_out->sin_family, (void *) &(addr_out->sin_addr), buf,
				 MAX_ADDR_STR_LEN) == NULL) {
			match_logdbg("__vma_sockaddr_to_vma: Converted IPv4 address is illegal");
		} else {
			match_logdbg("__vma_sockaddr_to_vma: Converted IPv4 is:%s", buf);
		}
		if (was_ipv6)
			*was_ipv6 = 1;

	} else if (sin->sin_family == 0) {

		match_logdbg("__vma_sockaddr_to_vma: Converted NULL address");
		memcpy(addr_out, addr_in, addrlen);
	} else {
		match_logdbg("Error __vma_sockaddr_to_vma: "
				  "address family <%d> is unknown", sin->sin_family);
		errno = EAFNOSUPPORT;
		return -1;
	}

	return 0;
}
