在 nginx 下运行的 Icecast2 无法连接

Osc*_* H. 3 streaming nginx broadcast stream icecast

我想开始说,我已经四处寻找这个问题的答案,但似乎没有其他人遇到过这个问题,或者没有人在做这个问题。所以,我最近在我的 Debian 服务器上安装了 icecast2,问题是我完全能够从连接到端口 8000 上的本地 IP 的本地网络向我的服务器广播,并在 radio.example.com 上通过互联网收听流因为我用 nginx 代理它,所以到目前为止完全没有问题。问题在于当我想广播到我用 nginx stream.example.com 提供的域时

我有两个理论,一个是代理没有给 icecast 提供源 IP,所以它认为它是从 127.0.0.1 广播的,另一个是 nginx 对数据流做了一些奇怪的事情,因此没有提供正确的格式给冰铸。

有什么想法吗?提前致谢!

这是nginx配置

server {
    listen 80;
    listen [::]:80;
    server_name radio.example.com;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Real-IP $remote_addr;


    location / {
            proxy_pass http://127.0.0.1:8000/radio;
            subs_filter_types application/xspf+xml audio/x-mpegurl audio/x-vclt text/css text/html text/xml;
            subs_filter ':80/' '/' gi;
            subs_filter '@localhost' '@stream.example.com' gi;
            subs_filter 'localhost' $host gi;
            subs_filter 'Mount Point ' $host gi;
    }
}

server {
    listen 80;
    listen [::]:80;
    server_name stream.example.com;

    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Real-IP $remote_addr;
    location / {
            proxy_pass http://localhost:8000/;
            subs_filter_types application/xspf+xml audio/x-mpegurl audio/x-vclt text/css text/html text/xml;
            subs_filter ':8000/' ':80/' gi;
            subs_filter '@localhost' '@stream.example.com' gi;
            subs_filter 'localhost' $host gi;
            subs_filter 'Mount Point ' $host gi;
    }
}
Run Code Online (Sandbox Code Playgroud)

这就是我在 icecast error.log 上得到的

[2018-08-10  14:15:45] INFO source/get_next_buffer End of Stream /radio
[2018-08-10  14:15:45] INFO source/source_shutdown Source from 127.0.0.1 at "/radioitavya" exiting
Run Code Online (Sandbox Code Playgroud)

mik*_*nik 8

不确定其中有多少与 OP 的问题直接相关,但这是我的配置中的一些片段。

这些是我在端口 443 上通过 SSL 向客户端提供流的块的基础知识。

在第一个位置块中,任何 URI 不是 /ogg、/128、/192 或 /320 的请求都被重写,以防止客户端访问来自 Icecast 服务器的任何输出,而不是流本身。

server {
  listen 443 ssl http2;
  server_name stream.example.com;
  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

  location / {
    rewrite  ~*(ogg) https://stream.example.com/ogg last;
    rewrite  ~*([0-1][0-5]\d) https://stream.example.com/128 last;
    rewrite  ~*(?|([1][6-9]\d)|([2]\d\d)) https://stream.example.com/192 last;
    rewrite  ~*([3-9]\d\d) https://stream.example.com/320 break;
    return  https://stream.example.com/320;
  }

  location ~ ^/(ogg|128|192|320)$ {
    proxy_bind $remote_addr transparent;
    set $stream_url http://192.168.100.100:8900/$1;
    types        { }
    default_type audio/mpeg;
    proxy_pass_request_headers on;
    proxy_set_header Access-Control-Allow-Origin *;
    proxy_set_header Host $host;
    proxy_set_header Range bytes=0-;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_buffering off;
    tcp_nodelay on;
    proxy_pass $stream_url;
  }
}
Run Code Online (Sandbox Code Playgroud)

proxy_bind使用transparent标志设置:

允许到代理服务器的传出连接源自非本地 IP 地址,例如,来自客户端的真实 IP 地址

这解决了日志/统计信息中本地 IP 地址而不是客户端 IP 的问题,为此,您还需要重新配置内核路由表以捕获从上游服务器发送的响应并将它们路由回 Nginx。

这需要 root 访问权限和对 Linux 网络配置的合理理解,我很欣赏不是每个人都有。我也很感激不是每个使用 Icecast 并且可能想要反向代理的人都会阅读这篇文章。一个更好的解决方案是让 Icecast 对 Nginx 更友好,所以我试了一下。

我从 github 克隆了 Icecast 并查看了代码。我可能错过了一些,但这些行看起来与我有关:

./src/logging.c:159:  client->con->ip,
./src/admin.c:700:    xmlNewTextChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "IP" : "ip"), XMLSTR(client->con->ip)); 
Run Code Online (Sandbox Code Playgroud)

对于不支持 PROXY 协议的服务器,Nginx 向上游传递客户端 IP 的默认方法是通过X-Real-IP标头。Icecast 似乎使用 的值client->con->ip来记录侦听器 IP。让我们稍微改变一下。我加了这个:

const char *realip;
realip = httpp_getvar (client->parser, "x-real-ip");
if (realip == NULL)
  realip = client->con->ip;
Run Code Online (Sandbox Code Playgroud)

并将前面的几行更改为:

./src/logging.c:163:  realip,
./src/admin.c:700:    xmlNewTextChild(node, NULL, XMLSTR(mode == OMODE_LEGACY ? "IP" : "ip"), XMLSTR(realip));
Run Code Online (Sandbox Code Playgroud)

然后我根据文档从源代码构建了 Icecast。proxy_set_header X-Real-IP $remote_addr;我的 Nginx conf 中的指令是传递客户端 IP,如果您有额外的上游服务器也处理请求,您将需要添加一些set_real_ip_from指定每个 IP 的指令,real_ip_recursive on;并使用$proxy_add_x_forwarded_for;将捕获处理请求的每个服务器的 IP 地址。

启动了我的新 Icecast 版本,这似乎工作得很好。如果X-Real-IP设置了标头,则 Icecast 将其记录为侦听器 IP,如果没有,则记录客户端请求 IP,因此它应该适用于反向代理和正常设置。看起来太简单了,也许我错过了@TBR?

好的,您现在应该通过 SSL 使用正确的统计信息/日志来提供有效的侦听器流。你已经完成了困难的部分。现在让我们向他们流式传输一些内容!

由于将流模块添加到 Nginx,因此无论是否使用 PUT/SOURCE,处理传入连接都很简单。

如果您在流指令中指定服务器,Nginx 将简单地将传入流隧道传输到上游服务器,而无需检查或修改数据包。Nginx 流配置第 101 课就是你所需要的:

stream {

  server {
    listen pub.lic.ip:port;
    proxy_pass ice.cast.ip:port;
  }
}
Run Code Online (Sandbox Code Playgroud)

我想毫无戒心的人们可能会在 Nginx 中遇到 SOURCE 连接的一个问题是在他们的 Nginx 配置中指定了错误的端口。不要难过,Shoutcast v1 只是很奇怪。要记住的要点是:

  • 它实际上会尝试连接到端口 +1,而不是您在客户端编码器中指定的端口

因此,如果您使用端口 8000 进行传入连接,请使用 Shoutcast v1 协议在客户端编码器中将端口设置为 7999,或者使用 2 个块设置 Nginx 流指令,一个用于端口 8000,一个用于端口 8001。

您的 Nginx 安装必须使用流模块构建,它不是标准构建的一部分。不确定?跑:

nginx -V 2>&1 | grep -qF -- --with-stream && echo ":)" || echo ":("
Run Code Online (Sandbox Code Playgroud)

如果你看到一张笑脸,你就可以走了。如果没有,您将需要构建 Nginx 并包含它。许多存储库都有一个nginx-extras包含流模块的包。

差不多完成了,我们现在需要的只是访问管理页面。我提供这些服务,https://example.com/icecast/但 Icecast 使用根路径生成管理页面链接中的所有 URI,不包括在内,icecast/因此它们将不起作用。让我们修复使用 Nginx 子过滤器模块添加icecast/到返回页面中的链接的问题:

location /icecast/ {
  sub_filter_types text/xhtml text/xml text/css;
  sub_filter 'href="/'  'href="/icecast/';
  sub_filter 'url(/'  'url(/icecast/';
  sub_filter_once off;
  sub_filter_last_modified on;
  proxy_set_header Accept-Encoding "";
  proxy_pass http://ice.cast.ip:port/;
}
Run Code Online (Sandbox Code Playgroud)

末尾的斜杠proxy_pass http://ice.cast.ip:port/;对于此工作至关重要。

如果 proxy_pass 指令被指定为 server:port,那么完整的原始客户端请求 URI 将被附加并传递给上游服务器。如果 proxy_pass 附加了任何 URI(甚至只是/),那么 Nginx 将用附加到 proxy_pass 的 URI替换与位置块(在这种情况下/icecast/)匹配的客户端请求 URI 部分。因此,通过附加斜杠,请求https://example.com/icecast/admin/将被代理到http://ice.cast.ip:port/admin/

最后,我不希望我的管理页面被全世界访问,只有我的 IP 和 LAN,所以我还在上面的位置包含了这些:

allow 127.0.0.1;
allow 192.168.1.0/24;
allow my.ip.add.ress;
deny all;
Run Code Online (Sandbox Code Playgroud)

就是这样。

sudo nginx -s reload
Run Code Online (Sandbox Code Playgroud)

玩得开心。