Skip to content
This repository was archived by the owner on Apr 5, 2026. It is now read-only.

Commit 33bdc72

Browse files
committed
feat: --stats-allow-net CIDR for custom stats access ranges
Overlay networks (Tailscale, Netbird, WireGuard) use non-RFC1918 ranges like 100.64.0.0/10 that is_private_ip() rejects. Add a repeatable flag to extend the stats endpoint allowlist with specific CIDR ranges, reusing the existing parser from net-ip-acl.c. Closes #4
1 parent fd71207 commit 33bdc72

5 files changed

Lines changed: 114 additions & 46 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [3.5.5] - 2026-03-30
6+
7+
### Added
8+
- `--stats-allow-net CIDR` flag to extend stats endpoint access beyond RFC1918 ranges (repeatable, e.g. `--stats-allow-net 100.64.0.0/10` for Tailscale/WireGuard). Docker: `STATS_ALLOW_NET=100.64.0.0/10,fd00::/8` ([#4](https://github.com/teleproxy/teleproxy/issues/4))
9+
510
## [3.5.4] - 2026-03-28
611

712
### Fixed

mtproto/mtproto-proxy.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1701,7 +1701,7 @@ int hts_stats_execute (connection_job_t c, struct raw_message *msg, int op) {
17011701
D->query_flags &= ~QF_KEEPALIVE;
17021702
return -501;
17031703
}
1704-
if (!is_private_ip(CONN_INFO(c)->remote_ip)) {
1704+
if (!is_private_ip(CONN_INFO(c)->remote_ip) && !ip_acl_check_stats_v4(CONN_INFO(c)->remote_ip)) {
17051705
return -404;
17061706
}
17071707

@@ -2612,6 +2612,12 @@ int f_parse_option (int val) {
26122612
case 2003:
26132613
direct_mode = 1;
26142614
break;
2615+
case 2004:
2616+
if (ip_acl_add_stats_net (optarg) < 0) {
2617+
kprintf ("invalid CIDR for --stats-allow-net: %s\n", optarg);
2618+
return 2;
2619+
}
2620+
break;
26152621
default:
26162622
return -1;
26172623
}
@@ -2633,6 +2639,7 @@ void mtfront_prepare_parse_options (void) {
26332639
parse_option ("ip-blocklist", required_argument, 0, 2001, "path to file with CIDR ranges to reject");
26342640
parse_option ("ip-allowlist", required_argument, 0, 2002, "path to file with CIDR ranges to exclusively allow");
26352641
parse_option ("direct", no_argument, 0, 2003, "connect directly to Telegram DCs instead of through ME relays (incompatible with -P)");
2642+
parse_option ("stats-allow-net", required_argument, 0, 2004, "CIDR range to allow stats access from, e.g. 100.64.0.0/10 (repeatable)");
26362643
}
26372644

26382645
void mtfront_parse_extra_args (int argc, char *argv[]) /* {{{ */ {

net/net-ip-acl.c

Lines changed: 82 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ struct ip_acl {
4343

4444
static struct ip_acl *current_blocklist;
4545
static struct ip_acl *current_allowlist;
46+
static struct ip_acl *stats_allowlist;
4647
static char *blocklist_file;
4748
static char *allowlist_file;
4849

@@ -221,6 +222,54 @@ static char *trim (char *s) {
221222
return s;
222223
}
223224

225+
/* Parse a CIDR string (e.g. "100.64.0.0/10", "2001:db8::/32", "10.0.0.1")
226+
into a rule struct. The input buffer is modified (slash replaced with NUL).
227+
Returns 0 on success, -1 on parse error. */
228+
static int ip_acl_parse_cidr (char *s, struct ip_acl_rule *rule) {
229+
memset (rule, 0, sizeof (*rule));
230+
231+
char *slash = strchr (s, '/');
232+
int explicit_prefix = 0;
233+
if (slash) {
234+
*slash = '\0';
235+
char *endptr;
236+
rule->prefix_len = (int)strtol (slash + 1, &endptr, 10);
237+
if (*endptr && !isspace ((unsigned char)*endptr)) {
238+
return -1;
239+
}
240+
explicit_prefix = 1;
241+
}
242+
243+
struct in_addr addr4;
244+
struct in6_addr addr6;
245+
246+
if (inet_pton (AF_INET, s, &addr4) == 1) {
247+
rule->family = AF_INET;
248+
rule->addr.v4 = ntohl (addr4.s_addr);
249+
if (!explicit_prefix) {
250+
rule->prefix_len = 32;
251+
}
252+
if (rule->prefix_len < 0 || rule->prefix_len > 32) {
253+
return -1;
254+
}
255+
rule->addr.v4 = mask_v4 (rule->addr.v4, rule->prefix_len);
256+
} else if (inet_pton (AF_INET6, s, &addr6) == 1) {
257+
rule->family = AF_INET6;
258+
memcpy (rule->addr.v6, addr6.s6_addr, 16);
259+
if (!explicit_prefix) {
260+
rule->prefix_len = 128;
261+
}
262+
if (rule->prefix_len < 0 || rule->prefix_len > 128) {
263+
return -1;
264+
}
265+
mask_v6 (rule->addr.v6, rule->prefix_len);
266+
} else {
267+
return -1;
268+
}
269+
270+
return 0;
271+
}
272+
224273
static struct ip_acl *ip_acl_load (const char *filename) {
225274
FILE *f = fopen (filename, "r");
226275
if (!f) {
@@ -241,56 +290,13 @@ static struct ip_acl *ip_acl_load (const char *filename) {
241290
lineno++;
242291
char *s = trim (line);
243292

244-
/* skip empty lines and comments */
245293
if (!*s || *s == '#') {
246294
continue;
247295
}
248296

249297
struct ip_acl_rule rule;
250-
memset (&rule, 0, sizeof (rule));
251-
252-
/* split on '/' for prefix length */
253-
char *slash = strchr (s, '/');
254-
int explicit_prefix = 0;
255-
if (slash) {
256-
*slash = '\0';
257-
char *endptr;
258-
rule.prefix_len = (int)strtol (slash + 1, &endptr, 10);
259-
if (*endptr && !isspace ((unsigned char)*endptr)) {
260-
kprintf ("ip_acl: %s:%d: invalid prefix length '%s'\n", filename, lineno, slash + 1);
261-
continue;
262-
}
263-
explicit_prefix = 1;
264-
}
265-
266-
/* try IPv4 first */
267-
struct in_addr addr4;
268-
struct in6_addr addr6;
269-
270-
if (inet_pton (AF_INET, s, &addr4) == 1) {
271-
rule.family = AF_INET;
272-
rule.addr.v4 = ntohl (addr4.s_addr);
273-
if (!explicit_prefix) {
274-
rule.prefix_len = 32;
275-
}
276-
if (rule.prefix_len < 0 || rule.prefix_len > 32) {
277-
kprintf ("ip_acl: %s:%d: invalid IPv4 prefix length %d\n", filename, lineno, rule.prefix_len);
278-
continue;
279-
}
280-
rule.addr.v4 = mask_v4 (rule.addr.v4, rule.prefix_len);
281-
} else if (inet_pton (AF_INET6, s, &addr6) == 1) {
282-
rule.family = AF_INET6;
283-
memcpy (rule.addr.v6, addr6.s6_addr, 16);
284-
if (!explicit_prefix) {
285-
rule.prefix_len = 128;
286-
}
287-
if (rule.prefix_len < 0 || rule.prefix_len > 128) {
288-
kprintf ("ip_acl: %s:%d: invalid IPv6 prefix length %d\n", filename, lineno, rule.prefix_len);
289-
continue;
290-
}
291-
mask_v6 (rule.addr.v6, rule.prefix_len);
292-
} else {
293-
kprintf ("ip_acl: %s:%d: cannot parse address '%s'\n", filename, lineno, s);
298+
if (ip_acl_parse_cidr (s, &rule) < 0) {
299+
kprintf ("ip_acl: %s:%d: cannot parse '%s'\n", filename, lineno, s);
294300
continue;
295301
}
296302

@@ -347,3 +353,34 @@ int ip_acl_blocklist_count (void) {
347353
int ip_acl_allowlist_count (void) {
348354
return current_allowlist ? current_allowlist->count : 0;
349355
}
356+
357+
int ip_acl_add_stats_net (const char *cidr) {
358+
char buf[256];
359+
int len = snprintf (buf, sizeof (buf), "%s", cidr);
360+
if (len <= 0 || len >= (int)sizeof (buf)) {
361+
return -1;
362+
}
363+
364+
char *s = trim (buf);
365+
if (!*s) {
366+
return -1;
367+
}
368+
369+
struct ip_acl_rule rule;
370+
if (ip_acl_parse_cidr (s, &rule) < 0) {
371+
return -1;
372+
}
373+
374+
if (!stats_allowlist) {
375+
stats_allowlist = ip_acl_alloc ();
376+
if (!stats_allowlist) {
377+
return -1;
378+
}
379+
}
380+
381+
return ip_acl_add_rule (stats_allowlist, &rule);
382+
}
383+
384+
int ip_acl_check_stats_v4 (unsigned ip) {
385+
return acl_contains_v4 (stats_allowlist, ip);
386+
}

net/net-ip-acl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ int ip_acl_check_v4 (unsigned ip);
3535
Returns 1 if allowed, 0 if rejected. */
3636
int ip_acl_check_v6 (const unsigned char ipv6[16]);
3737

38+
/* Add a CIDR range to the stats endpoint allowlist (called during CLI parsing).
39+
Can be called multiple times. Returns 0 on success, -1 on parse error. */
40+
int ip_acl_add_stats_net (const char *cidr);
41+
42+
/* Check whether an IPv4 address (host byte order) is in the stats allowlist.
43+
Returns 1 if allowed, 0 if not (default deny when no ranges configured). */
44+
int ip_acl_check_stats_v4 (unsigned ip);
45+
3846
/* Return count of loaded rules (for stats/debugging). */
3947
int ip_acl_blocklist_count (void);
4048
int ip_acl_allowlist_count (void);

start.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,17 @@ if [ -n "$IP_ALLOWLIST" ]; then
160160
CMD="$CMD --ip-allowlist $IP_ALLOWLIST"
161161
fi
162162

163+
if [ -n "$STATS_ALLOW_NET" ]; then
164+
_save_ifs="$IFS"
165+
IFS=','
166+
for _net in $STATS_ALLOW_NET; do
167+
IFS="$_save_ifs"
168+
_net=$(printf '%s' "$_net" | tr -d '[:space:]')
169+
[ -n "$_net" ] && CMD="$CMD --stats-allow-net $_net"
170+
done
171+
IFS="$_save_ifs"
172+
fi
173+
163174
if [ "$DIRECT_MODE" = "true" ]; then
164175
CMD="$CMD --direct -M $WORKERS -u mtproxy $ORIG_ARGS"
165176
echo "Direct mode: connecting directly to Telegram DCs (no ME relay)"

0 commit comments

Comments
 (0)