static const struct xt_icmp_names {
	const char *name;
	uint8_t type;
	uint8_t code_min, code_max;
} icmp_codes[] = {
	{ "any", 0xFF, 0, 0xFF },
	{ "echo-reply", 0, 0, 0xFF },
	/* Alias */ { "pong", 0, 0, 0xFF },

	{ "destination-unreachable", 3, 0, 0xFF },
	{   "network-unreachable", 3, 0, 0 },
	{   "host-unreachable", 3, 1, 1 },
	{   "protocol-unreachable", 3, 2, 2 },
	{   "port-unreachable", 3, 3, 3 },
	{   "fragmentation-needed", 3, 4, 4 },
	{   "source-route-failed", 3, 5, 5 },
	{   "network-unknown", 3, 6, 6 },
	{   "host-unknown", 3, 7, 7 },
	{   "network-prohibited", 3, 9, 9 },
	{   "host-prohibited", 3, 10, 10 },
	{   "TOS-network-unreachable", 3, 11, 11 },
	{   "TOS-host-unreachable", 3, 12, 12 },
	{   "communication-prohibited", 3, 13, 13 },
	{   "host-precedence-violation", 3, 14, 14 },
	{   "precedence-cutoff", 3, 15, 15 },

	{ "source-quench", 4, 0, 0xFF },

	{ "redirect", 5, 0, 0xFF },
	{   "network-redirect", 5, 0, 0 },
	{   "host-redirect", 5, 1, 1 },
	{   "TOS-network-redirect", 5, 2, 2 },
	{   "TOS-host-redirect", 5, 3, 3 },

	{ "echo-request", 8, 0, 0xFF },
	/* Alias */ { "ping", 8, 0, 0xFF },

	{ "router-advertisement", 9, 0, 0xFF },

	{ "router-solicitation", 10, 0, 0xFF },

	{ "time-exceeded", 11, 0, 0xFF },
	/* Alias */ { "ttl-exceeded", 11, 0, 0xFF },
	{   "ttl-zero-during-transit", 11, 0, 0 },
	{   "ttl-zero-during-reassembly", 11, 1, 1 },

	{ "parameter-problem", 12, 0, 0xFF },
	{   "ip-header-bad", 12, 0, 0 },
	{   "required-option-missing", 12, 1, 1 },

	{ "timestamp-request", 13, 0, 0xFF },

	{ "timestamp-reply", 14, 0, 0xFF },

	{ "info-request", 15, 0, 0xFF },
	{ "info-reply", 16, 0, 0xFF },

	{ "address-mask-request", 17, 0, 0xFF },

	{ "address-mask-reply", 18, 0, 0xFF }
}, icmpv6_codes[] = {
	{ "destination-unreachable", 1, 0, 0xFF },
	{   "no-route", 1, 0, 0 },
	{   "communication-prohibited", 1, 1, 1 },
	{   "beyond-scope", 1, 2, 2 },
	{   "address-unreachable", 1, 3, 3 },
	{   "port-unreachable", 1, 4, 4 },
	{   "failed-policy", 1, 5, 5 },
	{   "reject-route", 1, 6, 6 },

	{ "packet-too-big", 2, 0, 0xFF },

	{ "time-exceeded", 3, 0, 0xFF },
	/* Alias */ { "ttl-exceeded", 3, 0, 0xFF },
	{   "ttl-zero-during-transit", 3, 0, 0 },
	{   "ttl-zero-during-reassembly", 3, 1, 1 },

	{ "parameter-problem", 4, 0, 0xFF },
	{   "bad-header", 4, 0, 0 },
	{   "unknown-header-type", 4, 1, 1 },
	{   "unknown-option", 4, 2, 2 },

	{ "echo-request", 128, 0, 0xFF },
	/* Alias */ { "ping", 128, 0, 0xFF },

	{ "echo-reply", 129, 0, 0xFF },
	/* Alias */ { "pong", 129, 0, 0xFF },

	{ "mld-listener-query", 130, 0, 0xFF },

	{ "mld-listener-report", 131, 0, 0xFF },

	{ "mld-listener-done", 132, 0, 0xFF },
	/* Alias */ { "mld-listener-reduction", 132, 0, 0xFF },

	{ "router-solicitation", 133, 0, 0xFF },

	{ "router-advertisement", 134, 0, 0xFF },

	{ "neighbour-solicitation", 135, 0, 0xFF },
	/* Alias */ { "neighbor-solicitation", 135, 0, 0xFF },

	{ "neighbour-advertisement", 136, 0, 0xFF },
	/* Alias */ { "neighbor-advertisement", 136, 0, 0xFF },

	{ "redirect", 137, 0, 0xFF },
}, igmp_types[] = {
	{ "membership-query", 0x11 },
	{ "membership-report-v1", 0x12 },
	{ "membership-report-v2", 0x16 },
	{ "leave-group", 0x17 },
	{ "membership-report-v3", 0x22 },
};

static inline char *parse_range(const char *str, unsigned int res[])
{
	char *next;

	if (!xtables_strtoui(str, &next, &res[0], 0, 255))
		return NULL;

	res[1] = res[0];
	if (*next == ':') {
		str = next + 1;
		if (!xtables_strtoui(str, &next, &res[1], 0, 255))
			return NULL;
	}

	return next;
}

static void
__parse_icmp(const struct xt_icmp_names codes[], size_t n_codes,
	     const char *codes_name, const char *fmtstring,
	     uint8_t type[], uint8_t code[])
{
	unsigned int match = n_codes;
	unsigned int i, number[2];

	for (i = 0; i < n_codes; i++) {
		if (strncasecmp(codes[i].name, fmtstring, strlen(fmtstring)))
			continue;
		if (match != n_codes)
			xtables_error(PARAMETER_PROBLEM,
				      "Ambiguous %s type `%s': `%s' or `%s'?",
				      codes_name, fmtstring, codes[match].name,
				      codes[i].name);
		match = i;
	}

	if (match < n_codes) {
		type[0] = type[1] = codes[match].type;
		if (code) {
			code[0] = codes[match].code_min;
			code[1] = codes[match].code_max;
		}
	} else {
		char *next = parse_range(fmtstring, number);
		if (!next)
			xtables_error(PARAMETER_PROBLEM, "Unknown %s type `%s'",
				      codes_name, fmtstring);
		type[0] = (uint8_t) number[0];
		type[1] = (uint8_t) number[1];
		switch (*next) {
		case 0:
			if (code) {
				code[0] = 0;
				code[1] = 255;
			}
			return;
		case '/':
			if (!code)
				break;

			next = parse_range(next + 1, number);
			if (!next)
				xtables_error(PARAMETER_PROBLEM,
					      "Unknown %s code `%s'",
					      codes_name, fmtstring);
			code[0] = (uint8_t) number[0];
			code[1] = (uint8_t) number[1];
			if (!*next)
				break;
		/* fallthrough */
		default:
			xtables_error(PARAMETER_PROBLEM,
				      "unknown character %c", *next);
		}
	}
}

static inline void
__ipt_parse_icmp(const struct xt_icmp_names *codes, size_t n_codes,
		 const char *codes_name, const char *fmtstr,
		 uint8_t *type, uint8_t code[])
{
	uint8_t types[2];

	__parse_icmp(codes, n_codes, codes_name, fmtstr, types, code);
	if (types[1] != types[0])
		xtables_error(PARAMETER_PROBLEM,
			      "%s type range not supported", codes_name);
	*type = types[0];
}

static inline void
ipt_parse_icmp(const char *str, uint8_t *type, uint8_t code[])
{
	__ipt_parse_icmp(icmp_codes, ARRAY_SIZE(icmp_codes),
			 "ICMP", str, type, code);
}

static inline void
ipt_parse_icmpv6(const char *str, uint8_t *type, uint8_t code[])
{
	__ipt_parse_icmp(icmpv6_codes, ARRAY_SIZE(icmpv6_codes),
			 "ICMPv6", str, type, code);
}

static inline void
ebt_parse_icmp(const char *str, uint8_t type[], uint8_t code[])
{
	__parse_icmp(icmp_codes, ARRAY_SIZE(icmp_codes),
		     "ICMP", str, type, code);
}

static inline void
ebt_parse_icmpv6(const char *str, uint8_t type[], uint8_t code[])
{
	__parse_icmp(icmpv6_codes, ARRAY_SIZE(icmpv6_codes),
		     "ICMPv6", str, type, code);
}

static inline void
ebt_parse_igmp(const char *str, uint8_t type[])
{
	__parse_icmp(igmp_types, ARRAY_SIZE(igmp_types),
		     "IGMP", str, type, NULL);
}

static void xt_print_icmp_types(const struct xt_icmp_names *_icmp_codes,
				unsigned int n_codes)
{
	unsigned int i;

	for (i = 0; i < n_codes; ++i) {
		if (i && _icmp_codes[i].type == _icmp_codes[i-1].type) {
			if (_icmp_codes[i].code_min == _icmp_codes[i-1].code_min
			    && (_icmp_codes[i].code_max
				== _icmp_codes[i-1].code_max))
				printf(" (%s)", _icmp_codes[i].name);
			else
				printf("\n   %s", _icmp_codes[i].name);
		}
		else
			printf("\n%s", _icmp_codes[i].name);
	}
	printf("\n");
}
