Skip to content

Commit c4ca70e

Browse files
stackiaclaude
andcommitted
feat(http-proxy): add HTTP reverse proxy with multi-source r2h-token auth
Add HTTP reverse proxy feature for proxying upstream HTTP servers, primarily for HLS/DASH streaming. Key features: - URL format: /http/host[:port]/path with default port 80 - Full request passthrough (GET/POST/PUT/DELETE) - Complete header forwarding (User-Agent, Accept, Cookie, Authorization) - Response passthrough with chunked encoding support - Configurable upstream interface (upstream-interface-http) Add multi-source r2h-token authentication to solve HLS segment auth: - URL query parameter: ?r2h-token=xxx (highest priority) - Cookie header: Cookie: r2h-token=xxx - User-Agent: R2HTOKEN/xxx format When token is provided via URL query, server sends Set-Cookie header so subsequent requests (HLS segments) automatically authenticate. Update OpenWrt support with LuCI UI and init script changes. Update documentation with HTTP proxy usage and r2h-token explanation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 3457087 commit c4ca70e

29 files changed

Lines changed: 4992 additions & 3365 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ rtp2httpd 支持将组播 RTP/UDP 流、RTSP 流转换为 HTTP 单播流,并
1212
- **UDPxy 兼容性**:完全兼容 UDPxy URL 格式
1313
- **RTSP 转 HTTP 视频流**:完整支持 RTSP/RTP 协议栈,包括 TCP 和 UDP 传输模式
1414
- 可以实现 IPTV RTSP 时移源的回看
15+
- **HTTP 反向代理**:可以反向代理 IPTV 内网 HLS 源,方便在局域网、公网观看
1516
- **M3U 播放列表集成**:支持 M3U/M3U8 格式,自动识别并转换节目地址,提供标准化的播放列表
1617
- 支持外部 M3U URL
1718
- 智能识别 RTP/RTSP URL 并转换为 HTTP 代理格式

docs/configuration.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ rtp2httpd [选项]
2020
- `-f, --upstream-interface-fcc <接口>` - FCC 上游接口(覆盖 `-i` 设置)
2121
- `-t, --upstream-interface-rtsp <接口>` - RTSP 上游接口(覆盖 `-i` 设置)
2222
- `-r, --upstream-interface-multicast <接口>` - 组播上游接口(覆盖 `-i` 设置)
23+
- `-y, --upstream-interface-http <接口>` - HTTP 代理上游接口(覆盖 `-i` 设置)
2324

24-
**优先级规则**`upstream-interface-{fcc,rtsp,multicast}` > `upstream-interface` > 系统路由表
25+
**优先级规则**`upstream-interface-{fcc,rtsp,multicast,http}` > `upstream-interface` > 系统路由表
2526

2627
### 性能优化
2728

@@ -127,12 +128,13 @@ upstream-interface = eth0
127128
# upstream-interface-multicast = eth0 # 组播流量 (RTP/UDP)
128129
# upstream-interface-fcc = eth1 # FCC
129130
# upstream-interface-rtsp = eth2 # RTSP
131+
# upstream-interface-http = eth3 # HTTP 代理
130132
#
131133
# 混合配置示例:默认使用 eth0,但 FCC 使用更快的 eth1
132134
# upstream-interface = eth0
133135
# upstream-interface-fcc = eth1
134136
#
135-
# 优先级:upstream-interface-{multicast,fcc,rtsp} > upstream-interface > 系统路由表
137+
# 优先级:upstream-interface-{multicast,fcc,rtsp,http} > upstream-interface > 系统路由表
136138

137139
# 外部 M3U 配置(支持 file://, http://, https://)
138140
# 注意:HTTP/HTTPS 需要安装 curl 命令

docs/url-formats.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,58 @@ http://192.168.1.1:5140/rtsp/iptv.example.com:554/channel1?seek=20240101120000&r
7474

7575
关于时移回看的参数处理(时区、偏移),详见 [RTSP 时间处理与时区转换](rtsp-time-processing.md)
7676

77+
## HTTP 代理
78+
79+
```url
80+
http://服务器地址:端口/http/上游服务器[:端口]/路径[?参数]
81+
```
82+
83+
**示例**
84+
85+
```url
86+
# 代理 HLS 流(指定端口)
87+
http://192.168.1.1:5140/http/upstream.example.com:8080/live/stream.m3u8
88+
89+
# 代理 HTTP 请求(省略端口,默认 80)
90+
http://192.168.1.1:5140/http/api.example.com/video?auth=xxx&quality=hd
91+
```
92+
93+
### 参数说明
94+
95+
- **上游服务器**:目标 HTTP 服务器地址
96+
- **端口**(可选):目标服务器端口,默认 80
97+
- **路径**:请求路径,包括查询参数
98+
99+
### 使用场景
100+
101+
- 代理上游 HLS/DASH 流媒体,统一认证和访问控制
102+
- 为不支持直接访问的内网服务提供 HTTP 代理
103+
- 绕过防火墙限制,通过 rtp2httpd 转发 HTTP 请求
104+
105+
### 注意事项
106+
107+
- 仅支持 HTTP 上游(不支持 HTTPS)
108+
- 可通过 `upstream-interface-http` 配置指定上游网络接口
109+
110+
### r2h-token 认证
111+
112+
当配置了 r2h-token 时,HTTP 代理支持从多个来源获取认证令牌:
113+
114+
1. **URL 查询参数**(最高优先级):`?r2h-token=xxx`
115+
2. **Cookie**`Cookie: r2h-token=xxx`
116+
3. **User-Agent**`User-Agent: ... R2HTOKEN/xxx`
117+
118+
**为什么需要多来源认证?**
119+
120+
HTTP 代理的典型用途是代理上游 HLS 流媒体。HLS 协议的工作方式是:播放器先请求 `.m3u8` 索引文件,然后根据索引文件中的 URL 请求后续的 `.ts` 媒体分片。
121+
122+
问题在于:rtp2httpd 不会改写 m3u8 内容,因此索引文件中的分片 URL 不会包含 `r2h-token` 参数。如果只支持 URL 参数认证,播放器在请求后续分片时就会因为缺少 token 而被拒绝。
123+
124+
多来源认证解决了这个问题:
125+
126+
- **网页播放器**:首次请求 `/playlist.m3u?r2h-token=xxx` 时,服务器下发 `Set-Cookie: r2h-token=xxx`,浏览器后续请求自动携带 Cookie
127+
- **客户端播放器**:大多数客户端都允许修改 User-Agent,在 User-Agent 中添加 `R2HTOKEN/yourtoken`,所有请求都会自动认证
128+
77129
## M3U 播放列表访问
78130

79131
```url

openwrt-support/luci-app-rtp2httpd/htdocs/luci-static/resources/view/rtp2httpd.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,19 @@ return view.extend({
314314
o.datatype = "interface";
315315
o.depends({ use_config_file: "0", advanced_interface_settings: "1" });
316316

317+
o = s.taboption(
318+
"network",
319+
widgets.DeviceSelect,
320+
"upstream_interface_http",
321+
_("Upstream HTTP Proxy Interface"),
322+
_(
323+
"Interface to use for HTTP proxy upstream requests (default: use routing table)"
324+
)
325+
);
326+
o.noaliases = true;
327+
o.datatype = "interface";
328+
o.depends({ use_config_file: "0", advanced_interface_settings: "1" });
329+
317330
o = s.taboption(
318331
"network",
319332
form.Value,

openwrt-support/luci-app-rtp2httpd/po/zh_Hans/rtp2httpd.po

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,16 @@ msgstr "上游组播接口"
289289
msgid "Upstream RTSP Interface"
290290
msgstr "上游 RTSP 接口"
291291

292+
#: htdocs/luci-static/resources/view/rtp2httpd.js:321
293+
msgid "Upstream HTTP Proxy Interface"
294+
msgstr "上游 HTTP 代理接口"
295+
296+
#: htdocs/luci-static/resources/view/rtp2httpd.js:323
297+
msgid ""
298+
"Interface to use for HTTP proxy upstream requests (default: use routing "
299+
"table)"
300+
msgstr "用于 HTTP 代理上游请求的接口(默认:使用路由表)"
301+
292302
#: htdocs/luci-static/resources/view/rtp2httpd.js:198
293303
msgid "Use Config File"
294304
msgstr "使用配置文件"

openwrt-support/rtp2httpd/files/rtp2httpd.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ config rtp2httpd
1616
# option upstream_interface_multicast 'eth0'
1717
# option upstream_interface_fcc 'eth1'
1818
# option upstream_interface_rtsp 'eth2'
19+
# option upstream_interface_http 'eth3'
1920

2021
# option maxclients '5'
2122
# option workers '1'

openwrt-support/rtp2httpd/files/rtp2httpd.init

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ start_instance() {
4343
append_arg "$cfg" upstream_interface_multicast "--upstream-interface-multicast"
4444
append_arg "$cfg" upstream_interface_fcc "--upstream-interface-fcc"
4545
append_arg "$cfg" upstream_interface_rtsp "--upstream-interface-rtsp"
46+
append_arg "$cfg" upstream_interface_http "--upstream-interface-http"
4647
else
4748
# Simple mode - use default interface for all
4849
append_arg "$cfg" upstream_interface "--upstream-interface"

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ rtp2httpd_SOURCES = \
2020
fcc_huawei.c \
2121
stream.c \
2222
rtsp.c \
23+
http_proxy.c \
2324
stun.c \
2425
snapshot.c \
2526
timezone.c \
@@ -53,6 +54,7 @@ noinst_HEADERS = \
5354
fcc_huawei.h \
5455
stream.h \
5556
rtsp.h \
57+
http_proxy.h \
5658
stun.h \
5759
snapshot.h \
5860
timezone.h \

src/configuration.c

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ int cmd_upstream_interface_set = 0;
3939
int cmd_upstream_interface_fcc_set = 0;
4040
int cmd_upstream_interface_rtsp_set = 0;
4141
int cmd_upstream_interface_multicast_set = 0;
42+
int cmd_upstream_interface_http_set = 0;
4243
int cmd_fcc_listen_port_range_set = 0;
4344
int cmd_status_page_path_set = 0;
4445
int cmd_player_page_path_set = 0;
@@ -513,6 +514,14 @@ void parse_global_sec(char *line) {
513514
return;
514515
}
515516

517+
if (strcasecmp("upstream-interface-http", param) == 0) {
518+
if (set_if_not_cmd_override(cmd_upstream_interface_http_set,
519+
"upstream-interface-http")) {
520+
strncpy(config.upstream_interface_http, value, IFNAMSIZ - 1);
521+
}
522+
return;
523+
}
524+
516525
if (strcasecmp("mcast-rejoin-interval", param) == 0) {
517526
if (set_if_not_cmd_override(cmd_mcast_rejoin_interval_set,
518527
"mcast-rejoin-interval")) {
@@ -858,6 +867,8 @@ void config_init(void) {
858867
memset(config.upstream_interface_rtsp, 0, IFNAMSIZ);
859868
if (!cmd_upstream_interface_multicast_set)
860869
memset(config.upstream_interface_multicast, 0, IFNAMSIZ);
870+
if (!cmd_upstream_interface_http_set)
871+
memset(config.upstream_interface_http, 0, IFNAMSIZ);
861872

862873
/* External M3U settings (only if not set by command line) */
863874
if (!cmd_external_m3u_update_interval_set)
@@ -974,6 +985,8 @@ void usage(FILE *f, char *progname) {
974985
"traffic (overrides -i)\n"
975986
"\t-r --upstream-interface-multicast <interface> Interface for "
976987
"multicast traffic (overrides -i)\n"
988+
"\t-y --upstream-interface-http <interface> Interface for HTTP proxy "
989+
"upstream traffic (overrides -i)\n"
977990
"\t-R --mcast-rejoin-interval <seconds> Periodic multicast rejoin "
978991
"interval (0=disabled, default 0)\n"
979992
"\t-F --ffmpeg-path <path> Path to ffmpeg executable (default: ffmpeg)\n"
@@ -1048,6 +1061,7 @@ void parse_cmd_line(int argc, char *argv[]) {
10481061
{"upstream-interface-fcc", required_argument, 0, 'f'},
10491062
{"upstream-interface-rtsp", required_argument, 0, 't'},
10501063
{"upstream-interface-multicast", required_argument, 0, 'r'},
1064+
{"upstream-interface-http", required_argument, 0, 'y'},
10511065
{"mcast-rejoin-interval", required_argument, 0, 'R'},
10521066
{"ffmpeg-path", required_argument, 0, 'F'},
10531067
{"ffmpeg-args", required_argument, 0, 'A'},
@@ -1060,7 +1074,7 @@ void parse_cmd_line(int argc, char *argv[]) {
10601074
{"rtsp-stun-server", required_argument, 0, 'N'},
10611075
{0, 0, 0, 0}};
10621076

1063-
const char short_opts[] = "v:qhUm:w:b:B:c:l:P:H:XT:i:f:t:r:R:F:A:s:p:M:I:SCZN:";
1077+
const char short_opts[] = "v:qhUm:w:b:B:c:l:P:H:XT:i:f:t:r:y:R:F:A:s:p:M:I:SCZN:";
10641078
int option_index, opt;
10651079
int configfile_failed = 1;
10661080

@@ -1189,6 +1203,10 @@ void parse_cmd_line(int argc, char *argv[]) {
11891203
strncpy(config.upstream_interface_multicast, optarg, IFNAMSIZ - 1);
11901204
cmd_upstream_interface_multicast_set = 1;
11911205
break;
1206+
case 'y':
1207+
strncpy(config.upstream_interface_http, optarg, IFNAMSIZ - 1);
1208+
cmd_upstream_interface_http_set = 1;
1209+
break;
11921210
case 'R':
11931211
if (atoi(optarg) < 0) {
11941212
logger(LOG_ERROR, "Invalid mcast-rejoin-interval! Ignoring.");

src/configuration.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ typedef struct {
6767
char upstream_interface_multicast
6868
[IFNAMSIZ]; /* Interface for upstream multicast media requests (overrides
6969
upstream_interface) */
70+
char upstream_interface_http[IFNAMSIZ]; /* Interface for HTTP proxy upstream
71+
requests (overrides
72+
upstream_interface) */
7073

7174
/* Multicast settings */
7275
int mcast_rejoin_interval; /* Periodic multicast rejoin interval in seconds

0 commit comments

Comments
 (0)