虽然现在可以找到各种各样的第三方软件可以观看电视直播,
甚至央视频App官方也拥有九十多个频道可以一键投屏到电视上观看。
但总觉得是差点意思。总不能为了春节几天,去办一个IPTV吧?
突然想到,父母家其实一直都有IPTV,那么是否能“啃老”,白嫖父母家的IPTV呢?
那我们就来想想办法吧
过程中甚至发现了有可能是运营商提供的“福利”:非IPTV的用户,可能也可以观看单播直播源。
在文章的最后,也会将整理出来的单播源和组播源分享给大家,同地区的朋友可以尝试一下。
基本情况
【设备】
光猫:ZXHN F610EV9(光猫无WiFi,本身就是桥接模式,3个千兆口+1个iTV口)
主路由:Redmi AX3000(官方系统,WAN口接光猫千兆口,PPPoE模式,大内网)
交换机:H3C Mini S2G(接光猫的iTV口,为了保证不影响IPTV盒子的正常使用)
旁路由:友善R2S(OpenWrt系统,旁路由模式,WAN口接交换机,LAN口接主路由的LAN口)
IPTV盒子:运营商送的电视盒子(接交换机)
简单展示一下最终方案的网络拓补图,其实就是利用OpenWrt将IPTV的组播转单播,然后通过两地之间的私有网络进行传输数据。
这种方案虽然稍微有些复杂,但好在能保证既不影响原有的IPTV盒子的使用(与IPTV认证方式有关),也不会对原有网络产生什么影响。
但在实际操作的时候,也有不少细节和值得注意的地方,在此也将一一记录。
从整体上来说,总归是如下几步:
- IPTV认证方式
- 直播源抓取
- 组播转换
- 私有网络搭建
IPTV认证方式
搜索网上的资料,大部分提到的都是PPPoE认证,但还需要根据自己家IPTV的实际情况来看。
主要的方式,就是查看运营商给的IPTV盒子里的设置。
比如我这个IPTV盒子的以太网设置里没有选择PPPoE,就只是动态IP。
根据这个方式,所以我在光猫的iTV口下面又加了一个交换机,从理论上来说这样就可以依旧正常使用原来的IPTV盒子了。
经过测试之后,IPTV盒子依旧可以正常使用。
基于此,我们可以再继续折腾。
抓取直播源
在上一步中,我们知道了这个IPTV是不需要PPPoE认证的,那是否还有其他方式的认证呢?那只能通过抓包来查看了。
在这一步中主要有两种方式:
方式一就不过多介绍了,毕竟我在的这个是小地方,其他人分享的源未必适合我这里,所以这里主要说方式二。
通过查资料,发现网上大部分提供的抓包方案都是通过 Wireshark 来进行,主要就是因为 Wireshark 是可以抓包UDP的,而IPTV的直播流也都是通过UDP协议来传输的。
网上提供的抓包方案大概有两种:
- 使用具有端口镜像的交换机,将IPTV盒子发送的数据镜像给电脑的网卡,从而抓包。
- 在电脑上安装两个网口,分别接IPTV盒子和光猫iTV接口,并设置网络桥接,从而抓包。
但现在尴尬的是,现在手上既没有双网口的PC,也没有USB网卡,也没有具备端口镜像的交换机,只有一个刷了OpenWrt系统的R2S,但它也只有两个网口,那该怎么办呢?
从逻辑上来讲,UDP基本上只用于传输直播流,那么其他时候呢?比如电视盒子开机获取频道信息、获取推荐页面的信息流等信息呢?大概率使用的是http/https来进行传输的。
那如果是这样,我们就可以通过经常使用的 Charles 来进行抓包了,既然理论存在,我们就试试。
将电脑接在光猫iTV口下的交换机上,使电脑和IPTV盒子在同一个网段内。
并在IPTV盒子的网络设置中设置代理,填写电脑上 Charles 的IP和端口。
先不安装证书,碰碰运气,看看它用的是http还是https。
重启下 IPTV盒子。
紧接着,我们就能在Charles中看到了大量的请求。
大概看一下,里面包含了注册OTT设备、获取频道信息、广告信息等内容。
基本上能了解到,设备通过内置的userid、mac等信息,让服务器识别是用户身份的,但我们先不管这个。
那就有意思了,那就相当于我们一下子抓取到了全部频道的播放地址,看样子这里不仅包括了组播源,还包括了单播源,直接用电脑上的VLC打开网络串流验证一下,rtp://239.33.3.2:22580。
经过测试,我们发现在抓取的每个频道的流地址中,"hw" 中的单播源 和 "multi_HW" 中的组播源都是可以使用的。(可能是机房使用华为提供的服务?)
值得注意的是,我们现在测试直播源的PC,只是和IPTV盒子在同一网段下,并没有携带任何认证信息,但依旧可以播放抓取到的流地址。从而我们推断,也许服务器并不会对此进行鉴权。
现在我们也抓取到了直播源,也测试成功了,那么后面的一切都变得简单了。
因为 Charles 无法抓包UDP数据包,所以我们无从得知是否还有隐藏的认证方式,但通过测试是可以正常播放抓取到的组播源的,那就暂时先不管那么多了。
现在想想,如果只有R2S的话,也是可以使用OpenWrt创建网桥设备,再通过tcpdump来导出数据包来实现抓包UDP数据的。
还有一点有意思的是,在后续的测试中发现,抓取到的 "hw" 键值对中的单播源,即使是在光猫的非iTV接口下,也是可以播放的。难道说,即使我不办理IPTV只办理这个运营商的宽带,也是可以观看单播源的?难道这个是运营商给的福利?那么问题来了,如果不办理IPTV,只把设备接到iTV口上,是否可以播放组播源呢?(当然,现在这个IPTV也是套餐里送的……)
组播转换
既然上一步已经获取到了单播源,我们还需要再进行组播转换吗?
我认为其实还是有必要的,经过直播画面整点报时和实际时间对比,抓取到的单播源的直播延迟大概在50-60秒,而组播源的直播延迟只有10秒左右。所以,我们还是有必要自己进行一次组播转换。
这时候就要请出我们的友善R2S了,写入万能的OpenWrt系统。
可以根据自己的需要,直接按需求定制固件。比如这里就可以直接勾选旁路由模式,并设置好LAN口的IP和主路由的网关,这样刷写好固件后,直接插上网线,不需要进行设置就能直接使用。
如果是手动设置旁路由模式的话,也主要就是设置好LAN口的IP地址和网关就可以。
需要注意的是,在旁路由模式中,是将LAN口接在主路由的LAN口上哦,而不是将WAN口接在主路由的LAN上。在此案例中,我们关闭LAN口的DHCP服务。
接着,我们将WAN口设置为DHCP客户端,顺手也将主机名改一下,伪装一下
对于WAN口的其他设置,这个主要是看各自IPTV的实际情况,我这里其他设置保持默认就可以。
一切都准备好后,按照最开始提到的网络拓补图中的方式,将R2S接入到网络中。
此时已经可以看到WAN口已经正常获取到了动态的IP地址。
接着我们先配置一下OpenWrt防火墙的通信规则(网络 → 防火墙 → 通信规则)。
点击下方的 “添加” 按钮,我们新建一条规则,用来 允许所有组播网段的流量。
组播转换目前大家用的多的主要是两个插件,一个是 udpxy(luci-app-udpxy),一个是 msd_lite(luci-app-msd_lite),据说后者在低配置的设备上性能更好一些。
插件的配置也很简单,主要就是配置好 源接口 和 绑定地址 即可,其他设置都可以保持默认。
msd_lite设置
udpxy设置
udpxy 和 msd_lite 启用一个即可,我这里只是做一个演示。
如果一切正常的话,此时我们就能看到电视的直播画面了。
经过不严谨的测试发现,在我当前的设备上,msd_lite在转发的时候cpu的使用率大概在4%左右;udpxy大概在10%左右。所以后续都将使用 msd_lite。
经过如上设置,我们已经可以在家庭A的网络中观看电视直播了
私有网络搭建
接下来就是如何让家庭B也能观看电视直播,其实方案就是大家都知道的virtual**,这里使用的是Openvirtual**。Openvirtual**相对来说复杂一些,所以这里也只介绍该案例中用到的一些东西。
因为家庭A没有公网IP,家庭B有公网IP。所以将在 家庭B 的OpenWrt上架设服务端,而 家庭A R2S上的OpenWrt便作为客户端。
除此之外,还需要在家庭B的OpenWrt上配置ddns服务,以方便让客户端可以找到服务端,在此便不多介绍。
Openvirtual**服务端
在这里使用的是 “luci-app-openvirtual**” 插件,因为固件已经内置了插件并且创建好了tun0网络设备,所以这里就跳过这些设置(如果需要,可参考客户端中“配置网络接口”章节),只分享证书生成以及服务端配置文件。
生成证书文件
证书认证是Openvirtual**的主要认证方式,其中包括ca证书、dh密钥交换参数、服务器证书、服务器密钥、tls认证key、客户端证书和客户端密钥。
这里使用 easy-rsa来生成所有的证书,既可以在路由器中生成,也可以在本地中生成,方法都一样。
- 修改easy-rsa目录下 vars 配置(原名 vars.example ,复制重命名为 vars)
# 删掉注释符号,根据实际情况填写即可
set_var EASYRSA_REQ_COUNTRY "US"
set_var EASYRSA_REQ_PROVINCE "California"
set_var EASYRSA_REQ_CITY "San Francisco"
set_var EASYRSA_REQ_ORG "Copyleft Certificate Co"
set_var EASYRSA_REQ_EMAIL "me@example.net"
set_var EASYRSA_REQ_OU "My Organizational Unit"
#设置证书的有效期
set_var EASYRSA_CA_EXPIRE 3650
set_var EASYRSA_CERT_EXPIRE 3650
./easyrsa init-pki
./easyrsa build-ca # 如果不需要密码则增加 nopass 参数
过程中设置密码、Common Name,生成位置: ./pki/ca.crt
./easyrsa gen-req server nopass
设置Common Name后,会在 ./pki/private/ 目录下生成名为 server.key 的私钥
./easyrsa sign server server
过程中输入“yes” 确认信息,如果生成ca证书时设置了密码,还需要验证ca证书的密码。
会在./pki/issued/目录下生成名为 server.crt 的证书
./easyrsa gen-dh
生成位置:./pki/dh.pem
openvirtual** --genkey --secret /etc/openvirtual**/ta.key
此步骤非必须,如若使用tls认证key,则需要分别在客户端和服务端中增加相应的配置。
./easyrsa gen-req homeA nopass
过程中会设置Common Name,建议记住这个名称以方便后续进行管理,名称区分大小写。
会在./pki/private/目录下生成名为 homeA.key 的文件
./easyrsa sign-req client homeA
过程中输入“yes” 确认信息,如果生成ca证书时设置了密码,还需要验证ca证书的密码。
会在./pki/issued/目录下生成名为 homeA.crt 的证书
整理证书文件
上述过程中3、4、5、6四步中的证书,需要保存在服务器;3、8、9三步的证书,需要保存在客户端。
如果服务端中使用了 7,那么客户端中也得有 7 。如果后续要增加新的客户端证书,重复8、9两步即可。
我们将证书分别保存在方便找到的位置,服务器中可保存在 /etc/openvirtual** 文件夹下。
配置Openvirtual**服务端
打开Openvirtual**插件页面,我们可以使用自带的模板来进行配置。
添加完成后,来编辑配置,默认会进入基本配置,我们点击左上角的高级配置。
可以点击左下角的选项来添加选项,如果要删除选项,直接删掉文本框里的内容就行,或者选择 remove 选项。如果还不行,可以直接编辑 /etc/config/openvirtual** 文件。
要注意的是,编辑完每一页都需要保存一下。
服务设置保持默认即可,可以添加log来设置log输出用来排错
网络设置中,需要要把 topology 设置为 subnet 。这样在未来,我们才能为客户端绑定固定IP。有一点需要注意的失, topology 选项只有添加了 dev_type 选项并且设置 tun 后,才会出现在左下角的下拉框中。
virtual**设置中,主要就是添加 client_config_dir 设置,并且新建好目录,方便后面为客户端设置固定IP。
在push设置中,添加“route 10.10.10.0 255.255.255.0”以让客户端可以访问到家庭B的路由器网段。
其他的配置保持默认或根据实际情况调整即可。
加密算法设置中,分别设置好前面生成好的几个证书的位置,如果填写了 tls_auth ,那么在服务端一定要将 key_direction 设置为 0 ,而在客户端该项配置则为 1 。
其他都设置好后,我们回到基本配置中,添加 proto 选项,并设置为 tcp-server。
(不知道为啥,我的这个选项有时候就自己消失了……)
一切都设置好保存之后,可以返回页面,启用并启动服务。
如果启动失败,则可以根据log中的报错,来进行排查。
正常来说,如此设置之后,所有家庭B网络下的设备都可以和Openvirtual**连接进来的客户端互相访问。在OpenWrt的状态 → 路由下,也能看到 tun0 10.8.0.0/24 的路由信息。
Openvirtual**客户端
我们再回到家庭A的R2S上,这里要作为Openvirtual**的客户端,同样使用的是 “luci-app-openvirtual**” 插件。
制作Openvirtual**客户端配置文件
在Openvirtual**中,我们可以把客户端的证书以及配置都整合在一个后缀名为 ovirtual** 的文本文件中,方便传输到客户端中。
client
dev tun
proto tcp-client
remote virtual**.server.com 1194
resolv-retry infinite
nobind
persist-key
persist-tun
verb 3
<ca>
# ca证书文件内容
</ca>
<cert>
# 客户端证书文件内容
</cert>
<key>
# 客户端密钥文件内容
</key>
<tls-auth>
# tls认证key文件内容
</tls-auth>
key-direction 1
comp-lzo
将配置文件中 remote 后面修改为自己的域名或IP地址,如果服务端使用了 tls_auth ,那么客户端也需要添加 tls-auth 块,并将 key-direction 设置为 1 。检查配置没错后,我们保存为 HomeA.ovirtual** 。
配置网络接口
虽然安装了Openvirtual**插件,但似乎并没有自动添加相关的网络接口,这时就算客户端显示正在运行,但其实并没有连接到服务端中。
这里我们就来手动添加网络接口。
在 网络 → 接口 中,点击“添加新接口”。
为接口起一个名字 virtual**,协议选择“不配置协议”,在设备中选择 tun0 ,如果没有则在下拉框的“自定义”框中,填写 tun0。
接着,在接口的“防火墙设置”中,创建一个名为 virtual** 的防火墙区域。
之后,来到网络 → 防火墙中,将常规设置中的数据都改为“接受”(有可能会影响到后面访问客户端)
在下方,找到刚新建的virtual**区域,点击编辑。
在常规设置中,接受所有数据,并允许lan区域的转发。
在高级设置中,涵盖的设备选择 tun0,如果没有则在自定义框中添加。
都设置好之后,建议重启一下路由器,确保配置生效。
这时,在 网络→接口 中,我们会看到新建的virtual**接口提示“设备不存在”,但没关系。
配置Openvirtual**客户端
之前已经制作好了客户端的配置文件,这时我们只要在Openvirtual**插件页面将客户端文件上传即可。
如果没有意外的话,启用配置文件,就可以正常运行了。
这时候,我们看 网络 → 接口 中的virtual**接口已经有接收和发送的数据了。
如果这时候还是没有virtual**接口的数据,那可以尝试重启路由器、删掉Openvirtual**配置文件重新添加。
在终端中,输入 ip addr ,即可看到 tun0 设备中获取到的IP地址。
Openvirtual**客户端绑定IP
现在两地之间的网络已经互通了,但现在R2S在virtual**中的IP还是动态的,接下来我们给客户端绑定IP。
还记得在配置服务端时的virtual**设置中的 client_config_dir 吗?
还记得在生成客户端证书时设置的 Common Name 吗?
这时,我们在 client_config_dir 配置的文件夹下,新建一个名为客户端Common Name的文件,这里就是 HomeA。(如果忘记了Common Name,可以在证书文件中查看,一定要注意大小写!)在文件中写入下面的配置,意为:将名为HomeA客户端的IP设置为10.8.0.200:
ifconfig-push 10.8.0.200 255.255.255.0
分别重启服务端和客户端后,R2S获取的virtual**的IP地址已经变为了绑定的IP地址。
如果在连接的时候报错,请检查服务端网络设置中 topology 是否设置为 subnet 。
或者使用同样的客户端配置文件,在PC端或者手机端连接Openvirtual**进行排错。
其他配置
通过Watchcat检查virtual**连通性
因为服务端还是客户端都是自己家里的设备,无法保证一直在线。一旦作为服务端的OpenWrt重启后,客户端的virtual**断开后便不会自动重连。
所以,使用Watchcat插件间隔检查与服务端的连通性,若ping不通时,则自动重启,以保证R2S随时在线。
用脚本整理所有的直播源
用一个简单的脚本,将抓取到的直播源进行整理
# iptv_m3u8.j2
#EXTM3U
{%- for ch in channel_list %}
#EXTINF:-1,{{ ch.get('name') }}
{{ ch.get('playurl') }}
{%- endfor -%}
# -----------------------------------------
# main.py
import json, os, time
from jinja2 import Environment, select_autoescape, FileSystemLoader
PROXYURL = 'http://10.8.0.200:7088/'
SOURCE_FILE_PATH = 'channel_1.js'
with open(SOURCE_FILE_PATH, 'r+') as f:
json_data = json.loads(f.read())
channels_source = json_data[zxsq-anti-bbcode-0].get('channelList')
env = Environment(
loader=FileSystemLoader(os.getcwd()),
autoescape=select_autoescape()
)
template = env.get_template('iptv_m3u8.j2')
channel_list = []
channel_list2 = []
for channel in channels_source:
name = channel.get('name')
hw = channel.get('multi_HW')
hw = hw.replace('rtp://', 'rtp/')
play_url = f'{PROXYURL}{hw}'
_info = {
'name': name,
'playurl': play_url
}
_info2 = {
'name': name,
'playurl': channel.get('hw')
}
channel_list.append(_info)
channel_list2.append(_info2)
m3u8 = template.render(channel_list=channel_list)
m3u82 = template.render(channel_list=channel_list2)
with open(f'iptv_{int(time.time())}.m3u8', 'w+') as f:
f.write(m3u8)
with open(f'iptv2_{int(time.time())}.m3u8', 'w+') as f:
f.write(m3u82)
生成带台标及EPG的直播源
但可能个别频道识别有误,导致EPG没那么匹配,自己检查一下即可。
至此,我们已经可以在家庭B中观看家庭A中的IPTV了,并且很稳定。
经过测试,1080P清晰度的频道需要的带宽大概是 1MB/s ,4K清晰度的频道需要的带宽大概是 3-4MB/s ,所以在带宽上也都不是问题。
请不要胡乱输入以及粘贴、复制等方式灌水
请尊重作者、并共同维护网站的正常阅读,否则账户将会被限制发帖、回帖,并且积分可能会被清零,站内短信以及阅读权限等都会受到影响,谢谢。
具体限制方式:https://www.right.com.cn/forum/thread-8307840-1-1.html
|