1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
| | // SPDX-License-Identifier: GPL-2.0-or-later
/* PASST - Plug A Simple Socket Transport
* for qemu/UNIX domain socket mode
*
* PASTA - Pack A Subtle Tap Abstraction
* for network namespace/tap device mode
*
* PESTO - Programmable Extensible Socket Translation Orchestrator
* front-end for passt(1) and pasta(1) forwarding configuration
*
* fwd_rule.c - Helpers for working with forwarding rule specifications
*
* Copyright Red Hat
* Author: David Gibson <david@gibson.dropbear.id.au>
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include "fwd_rule.h"
#include "lineread.h"
#include "log.h"
/* Ephemeral port range: values from RFC 6335 */
static in_port_t fwd_ephemeral_min = (1 << 15) + (1 << 14);
static in_port_t fwd_ephemeral_max = NUM_PORTS - 1;
#define PORT_RANGE_SYSCTL "/proc/sys/net/ipv4/ip_local_port_range"
/** fwd_probe_ephemeral() - Determine what ports this host considers ephemeral
*
* Work out what ports the host thinks are emphemeral and record it for later
* use by fwd_port_is_ephemeral(). If we're unable to probe, assume the range
* recommended by RFC 6335.
*/
void fwd_probe_ephemeral(void)
{
char *line, *tab, *end;
struct lineread lr;
long min, max;
ssize_t len;
int fd;
fd = open(PORT_RANGE_SYSCTL, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
warn_perror("Unable to open %s", PORT_RANGE_SYSCTL);
return;
}
lineread_init(&lr, fd);
len = lineread_get(&lr, &line);
close(fd);
if (len < 0)
goto parse_err;
tab = strchr(line, '\t');
if (!tab)
goto parse_err;
*tab = '\0';
errno = 0;
min = strtol(line, &end, 10);
if (*end || errno)
goto parse_err;
errno = 0;
max = strtol(tab + 1, &end, 10);
if (*end || errno)
goto parse_err;
if (min < 0 || min >= (long)NUM_PORTS ||
max < 0 || max >= (long)NUM_PORTS)
goto parse_err;
fwd_ephemeral_min = min;
fwd_ephemeral_max = max;
return;
parse_err:
warn("Unable to parse %s", PORT_RANGE_SYSCTL);
}
/**
* fwd_port_map_ephemeral() - Mark ephemeral ports in a bitmap
* @map: Bitmap to update
*/
void fwd_port_map_ephemeral(uint8_t *map)
{
unsigned port;
for (port = fwd_ephemeral_min; port <= fwd_ephemeral_max; port++)
bitmap_set(map, port);
}
/**
* fwd_rule_addr() - Return match address for a rule
* @rule: Forwarding rule
*
* Return: matching address for rule, NULL if it matches all addresses
*/
const union inany_addr *fwd_rule_addr(const struct fwd_rule *rule)
{
if (rule->flags & FWD_DUAL_STACK_ANY)
return NULL;
return &rule->addr;
}
/**
* fwd_rule_fmt() - Prettily format forwarding rule as a string
* @rule: Rule to format
* @dst: Buffer to store output (should have FWD_RULE_STRLEN bytes)
* @size: Size of @dst
*/
#if defined(__GNUC__) && __GNUC__ < 15
/* Workaround bug in gcc 12, 13 & 14 (at least) which gives a false positive
* -Wformat-overflow message if this function is inlined.
*/
__attribute__((noinline))
#endif
const char *fwd_rule_fmt(const struct fwd_rule *rule, char *dst, size_t size)
{
const char *percent = *rule->ifname ? "%" : "";
const char *weak = "", *scan = "";
char addr[INANY_ADDRSTRLEN];
int len;
inany_ntop(fwd_rule_addr(rule), addr, sizeof(addr));
if (rule->flags & FWD_WEAK)
weak = " (best effort)";
if (rule->flags & FWD_SCAN)
scan = " (auto-scan)";
if (rule->first == rule->last) {
len = snprintf(dst, size,
"%s [%s]%s%s:%hu => %hu %s%s",
ipproto_name(rule->proto), addr, percent,
rule->ifname, rule->first, rule->to, weak, scan);
} else {
in_port_t tolast = rule->last - rule->first + rule->to;
len = snprintf(dst, size,
"%s [%s]%s%s:%hu-%hu => %hu-%hu %s%s",
ipproto_name(rule->proto), addr, percent,
rule->ifname, rule->first, rule->last,
rule->to, tolast, weak, scan);
}
if (len < 0 || (size_t)len >= size)
return NULL;
return dst;
}
/**
* fwd_rules_info() - Print forwarding rules for debugging
* @fwd: Table to print
*/
void fwd_rules_info(const struct fwd_rule *rules, size_t count)
{
unsigned i;
for (i = 0; i < count; i++) {
char buf[FWD_RULE_STRLEN];
info(" %s", fwd_rule_fmt(&rules[i], buf, sizeof(buf)));
}
}
/**
* fwd_rule_conflicts() - Test if two rules conflict with each other
* @a, @b: Rules to test
*/
static bool fwd_rule_conflicts(const struct fwd_rule *a, const struct fwd_rule *b)
{
if (a->proto != b->proto)
/* Non-conflicting protocols */
return false;
if (!inany_matches(fwd_rule_addr(a), fwd_rule_addr(b)))
/* Non-conflicting addresses */
return false;
assert(a->first <= a->last && b->first <= b->last);
if (a->last < b->first || b->last < a->first)
/* Port ranges don't overlap */
return false;
return true;
}
/**
* fwd_rule_conflict_check() - Die if given rule conflicts with any in list
* @new: New rule
* @rules: Existing rules against which to test
* @count: Number of rules in @rules
*/
void fwd_rule_conflict_check(const struct fwd_rule *new,
const struct fwd_rule *rules, size_t count)
{
unsigned i;
for (i = 0; i < count; i++) {
char newstr[FWD_RULE_STRLEN], rulestr[FWD_RULE_STRLEN];
if (!fwd_rule_conflicts(new, &rules[i]))
continue;
die("Forwarding configuration conflict: %s versus %s",
fwd_rule_fmt(new, newstr, sizeof(newstr)),
fwd_rule_fmt(&rules[i], rulestr, sizeof(rulestr)));
}
}
|