Allow setting alias port ranges in libalias and ipfw. This will allow a system

to be a true RFC 6598 NAT444 setup, where each network segment (e.g. user,
subnet) can have their own dedicated port aliasing ranges.

Reviewed by:		donner, kp
Approved by:		0mp (mentor), donner, kp
Differential Revision:	https://reviews.freebsd.org/D23450
This commit is contained in:
Neel Chauhan 2021-02-02 13:24:17 -08:00
parent 064e52c2d8
commit a08cdb6cfb
10 changed files with 186 additions and 5 deletions

View file

@ -3271,6 +3271,9 @@ Reverse the way libalias handles aliasing.
Obey transparent proxy rules only, packet aliasing is not performed.
.It Cm skip_global
Skip instance in case of global state lookup (see below).
.It Cm port_range Ar lower-upper
Set the aliasing ports between the ranges given. Upper port has to be greater
than lower.
.El
.Pp
Some specials value can be supplied instead of

View file

@ -285,6 +285,7 @@ enum tokens {
TOK_STATES_CHUNKS,
TOK_JMAXLEN,
TOK_PORT_RANGE,
TOK_PORT_ALIAS,
TOK_HOST_DEL_AGE,
TOK_PG_DEL_AGE,
TOK_TCP_SYN_AGE,

View file

@ -45,7 +45,8 @@ help(void)
"[pipe|queue] {zero|delete|show} [N{,N}]\n"
"nat N config {ip IPADDR|if IFNAME|log|deny_in|same_ports|unreg_only|unreg_cgn|\n"
" reset|reverse|proxy_only|redirect_addr linkspec|\n"
" redirect_port linkspec|redirect_proto linkspec}\n"
" redirect_port linkspec|redirect_proto linkspec|\n"
" port_range lower-upper}\n"
"set [disable N... enable N...] | move [rule] X to Y | swap X Y | show\n"
"set N {show|list|zero|resetlog|delete} [N{,N}] | flush\n"
"table N {add ip[/bits] [value] | delete ip[/bits] | flush | list}\n"

View file

@ -65,6 +65,7 @@ static struct _s_x nat_params[] = {
{ "reset", TOK_RESET_ADDR },
{ "reverse", TOK_ALIAS_REV },
{ "proxy_only", TOK_PROXY_ONLY },
{ "port_range", TOK_PORT_ALIAS },
{ "redirect_addr", TOK_REDIR_ADDR },
{ "redirect_port", TOK_REDIR_PORT },
{ "redirect_proto", TOK_REDIR_PROTO },
@ -753,12 +754,35 @@ nat_show_cfg(struct nat44_cfg_nat *n, void *arg __unused)
printf("\n");
}
static int
nat_port_alias_parse(char *str, u_short *lpout, u_short *hpout) {
long lp, hp;
char *ptr;
/* Lower port parsing */
lp = (long) strtol(str, &ptr, 10);
if (lp < 1024 || lp > 65535)
return 0;
if (!ptr || *ptr != '-')
return 0;
/* Upper port parsing */
hp = (long) strtol(ptr, &ptr, 10);
if (hp < 1024 || hp > 65535)
return 0;
if (ptr)
return 0;
*lpout = (u_short) lp;
*hpout = (u_short) hp;
return 1;
}
void
ipfw_config_nat(int ac, char **av)
{
ipfw_obj_header *oh;
struct nat44_cfg_nat *n; /* Nat instance configuration. */
int i, off, tok, ac1;
u_short lp, hp;
char *id, *buf, **av1, *end;
size_t len;
@ -786,6 +810,7 @@ ipfw_config_nat(int ac, char **av)
switch (tok) {
case TOK_IP:
case TOK_IF:
case TOK_PORT_ALIAS:
ac1--;
av1++;
break;
@ -925,8 +950,24 @@ ipfw_config_nat(int ac, char **av)
n->redir_cnt++;
off += i;
break;
case TOK_PORT_ALIAS:
if (ac == 0)
errx(EX_DATAERR, "missing option");
if (!nat_port_alias_parse(av[0], &lp, &hp))
errx(EX_DATAERR,
"You need a range of port(s) from 1024 <= x < 65536");
if (lp >= hp)
errx(EX_DATAERR,
"Upper port has to be greater than lower port");
n->alias_port_lo = lp;
n->alias_port_hi = hp;
ac--;
av++;
break;
}
}
if (n->mode & PKT_ALIAS_SAME_PORTS && n->alias_port_lo)
errx(EX_DATAERR, "same_ports and port_range cannot both be selected");
i = do_set3(IP_FW_NAT44_XCONFIG, &oh->opheader, len);
if (i != 0)

View file

@ -549,6 +549,8 @@ struct nat44_cfg_nat {
struct in_addr ip; /* nat IPv4 address */
uint32_t mode; /* aliasing mode */
uint32_t redir_cnt; /* number of entry in spool chain */
u_short alias_port_lo; /* low range for port aliasing */
u_short alias_port_hi; /* high range for port aliasing */
};
/* Nat command. */

View file

@ -86,6 +86,7 @@ struct alias_link;
/* Initialization and control functions. */
struct libalias *LibAliasInit(struct libalias *);
void LibAliasSetAddress(struct libalias *, struct in_addr _addr);
void LibAliasSetAliasPortRange(struct libalias *la, u_short port_low, u_short port_hi);
void LibAliasSetFWBase(struct libalias *, unsigned int _base, unsigned int _num);
void LibAliasSetSkinnyPort(struct libalias *, unsigned int _port);
unsigned int

View file

@ -595,6 +595,11 @@ GetNewPort(struct libalias *la, struct alias_link *lnk, int alias_port_param)
*/
port_net = lnk->src_port;
port_sys = ntohs(port_net);
} else if (la->aliasPortLower) {
/* First trial is a random port in the aliasing range. */
port_sys = la->aliasPortLower +
(arc4random() % la->aliasPortLength);
port_net = htons(port_sys);
} else {
/* First trial and all subsequent are random. */
port_sys = arc4random() & ALIAS_PORT_MASK;
@ -647,9 +652,15 @@ GetNewPort(struct libalias *la, struct alias_link *lnk, int alias_port_param)
}
#endif
}
port_sys = arc4random() & ALIAS_PORT_MASK;
port_sys += ALIAS_PORT_BASE;
port_net = htons(port_sys);
if (la->aliasPortLower) {
port_sys = la->aliasPortLower +
(arc4random() % la->aliasPortLength);
port_net = htons(port_sys);
} else {
port_sys = arc4random() & ALIAS_PORT_MASK;
port_sys += ALIAS_PORT_BASE;
port_net = htons(port_sys);
}
}
#ifdef LIBALIAS_DEBUG
@ -2381,6 +2392,19 @@ LibAliasSetAddress(struct libalias *la, struct in_addr addr)
LIBALIAS_UNLOCK(la);
}
void
LibAliasSetAliasPortRange(struct libalias *la, u_short port_low,
u_short port_high)
{
LIBALIAS_LOCK(la);
la->aliasPortLower = port_low;
/* Add 1 to the aliasPortLength as modulo has range of 1 to n-1 */
la->aliasPortLength = port_high - port_low + 1;
LIBALIAS_UNLOCK(la);
}
void
LibAliasSetTarget(struct libalias *la, struct in_addr target_addr)
{

View file

@ -163,6 +163,10 @@ struct libalias {
struct in_addr true_addr; /* in network byte order. */
u_short true_port; /* in host byte order. */
/* Port ranges for aliasing. */
u_short aliasPortLower;
u_short aliasPortLength;
/*
* sctp code support
*/

View file

@ -93,6 +93,8 @@ struct cfg_nat {
/* chain of redir instances */
LIST_HEAD(redir_chain, cfg_redir) redir_chain;
char if_name[IF_NAMESIZE]; /* interface name */
u_short alias_port_lo; /* low range for port aliasing */
u_short alias_port_hi; /* high range for port aliasing */
};
static eventhandler_tag ifaddr_event_tag;
@ -528,9 +530,12 @@ nat44_config(struct ip_fw_chain *chain, struct nat44_cfg_nat *ucfg)
ptr->ip = ucfg->ip;
ptr->redir_cnt = ucfg->redir_cnt;
ptr->mode = ucfg->mode;
ptr->alias_port_lo = ucfg->alias_port_lo;
ptr->alias_port_hi = ucfg->alias_port_hi;
strlcpy(ptr->if_name, ucfg->if_name, sizeof(ptr->if_name));
LibAliasSetMode(ptr->lib, ptr->mode, ~0);
LibAliasSetAddress(ptr->lib, ptr->ip);
LibAliasSetAliasPortRange(ptr->lib, ptr->alias_port_lo, ptr->alias_port_hi);
/*
* Redir and LSNAT configuration.
@ -658,6 +663,8 @@ export_nat_cfg(struct cfg_nat *ptr, struct nat44_cfg_nat *ucfg)
ucfg->ip = ptr->ip;
ucfg->redir_cnt = ptr->redir_cnt;
ucfg->mode = ptr->mode;
ucfg->alias_port_lo = ptr->alias_port_lo;
ucfg->alias_port_hi = ptr->alias_port_hi;
strlcpy(ucfg->if_name, ptr->if_name, sizeof(ucfg->if_name));
}

View file

@ -147,10 +147,107 @@ userspace_nat_cleanup()
firewall_cleanup $firewall
}
common_cgn() {
firewall=$1
portalias=$2
firewall_init $firewall
nat_init $firewall
epair_host_nat=$(vnet_mkepair)
epair_client1_nat=$(vnet_mkepair)
epair_client2_nat=$(vnet_mkepair)
vnet_mkjail nat ${epair_host_nat}b ${epair_client1_nat}a ${epair_client2_nat}a
vnet_mkjail client1 ${epair_client1_nat}b
vnet_mkjail client2 ${epair_client2_nat}b
ifconfig ${epair_host_nat}a 198.51.100.2/24 up
jexec nat ifconfig ${epair_host_nat}b 198.51.100.1/24 up
jexec nat ifconfig ${epair_client1_nat}a 100.64.0.1/24 up
jexec client1 ifconfig ${epair_client1_nat}b 100.64.0.2/24 up
jexec nat ifconfig ${epair_client2_nat}a 100.64.1.1/24 up
jexec client2 ifconfig ${epair_client2_nat}b 100.64.1.2/24 up
jexec nat sysctl net.inet.ip.forwarding=1
jexec client1 route add -net 198.51.100.0/24 100.64.0.1
jexec client2 route add -net 198.51.100.0/24 100.64.1.1
# ping fails without NAT configuration
atf_check -s exit:2 -o ignore jexec client1 ping -t 1 -c 1 198.51.100.2
atf_check -s exit:2 -o ignore jexec client2 ping -t 1 -c 1 198.51.100.2
if [[ $portalias ]]; then
firewall_config nat $firewall \
"ipfw" \
"ipfw -q nat 123 config if ${epair_host_nat}b unreg_cgn port_alias 2000-2999" \
"ipfw -q nat 456 config if ${epair_host_nat}b unreg_cgn port_alias 3000-3999" \
"ipfw -q add 1000 nat 123 all from any to 198.51.100.2 2000-2999 in via ${epair_host_nat}b" \
"ipfw -q add 2000 nat 456 all from any to 198.51.100.2 3000-3999 in via ${epair_host_nat}b" \
"ipfw -q add 3000 nat 123 all from 100.64.0.2 to any out via ${epair_host_nat}b" \
"ipfw -q add 4000 nat 456 all from 100.64.1.2 to any out via ${epair_host_nat}b"
else
firewall_config nat $firewall \
"ipfw" \
"ipfw -q nat 123 config if ${epair_host_nat}b unreg_cgn" \
"ipfw -q add 1000 nat 123 all from any to any"
fi
# ping is successful now
atf_check -s exit:0 -o ignore jexec client1 ping -t 1 -c 1 198.51.100.2
atf_check -s exit:0 -o ignore jexec client2 ping -t 1 -c 1 198.51.100.2
# if portalias, test a tcp server/client with nc
if [[ $portalias ]]; then
for inst in 1 2; do
daemon nc -p 198.51.100.2 7
atf_check -s exit:0 -o ignore jexec client$inst sh -c "echo | nc -N 198.51.100.2 7"
done
fi
}
cgn_head()
{
atf_set descr 'IPv4 CGN (RFC 6598) test'
atf_set require.user root
}
cgn_body()
{
common_cgn $1 false
}
cgn_cleanup()
{
firewall_cleanup ipfw
}
portalias_head()
{
atf_set descr 'IPv4 CGN (RFC 6598) port aliasing test'
atf_set require.user root
}
portalias_body()
{
common_cgn $1 true
}
portalias_cleanup()
{
firewall_cleanup ipfw
}
setup_tests \
basic \
pf \
ipfw \
ipfnat \
userspace_nat \
ipfw
ipfw \
cgn \
ipfw \
portalias \
ipfw