家庭网络拓扑及IPTV单线复用方案

搬新家快接近一年了,家里网络布局经过多轮设备更替和演进,总算稳定下来。主要介绍下当前网络的拓扑结构,以及如何利用弱电箱到客厅的一根网线,实现既可以上网,又可以看 IPTV。

家庭网络拓扑介绍

需求分析

  1. 网络套餐为千兆,为保证 WIFI 信号质量不成为带宽的约束,且确保智能家居的接入稳定,需每个房间放置无线接入点;
  2. 家庭接入设备量大,存在更多网络需求,需软路由进行拨号、DHCP、DNS等功能;
  3. 弱点箱到客厅仅预埋一根网线,需支持上网和IPTV两个功能;
  4. 尽可能利用当前预埋线,不另走明线破坏装修。

实现方案

整体网络拓扑大致由 光猫 —— 路由 —— 二层交换机 —— AP —— 接入设备 组成,呈现 树状结构,避免网络环路导致的网络风暴。

特点如下:

  1. 光猫 Internet 网口修改为桥接,仅作光电调制解调(modem)功能;
  2. 软路由和光猫同放弱电箱,软路由作为家庭网络中枢,实现以下功能:
    • 拨号上网,且多播实现宽带叠加;
    • 网关,同时存在一般网关和科学上网网关;
    • DNS 服务,实现去广告和加速功能;
    • DHCP 服务,根据设备 mac 分配分配上述功能,且带IPv6;
  3. 增配网管型交换机实现 VLAN 划分,分离 IPTV 线路,同时增加网口供更多设备使用;
  4. 多台无线路由器组Mesh,保证全屋 WIFI 覆盖,取消 DHCP 服务,当作 AP(Access Point) 使用;
  5. 搭建服务器,提供 NAS 存储服务供家庭数据存储,提供虚拟机服务供开发。

利用 VLAN 实现单线复用接入 IPTV

因为 IPTV 的特殊性,最佳是光猫 IPTV 接口走单独网线直达机顶盒,避免和家庭网络的 Internet 存在冲突。
实际上因为弱电箱到客厅预埋线仅一根,必须实现单线复用,且做好区分,才能保证两者独立工作正常,解决方案就是 划分 VLAN。

VLAN(Virtual Local Area Network)即虚拟局域网,是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。
每个VLAN是一个广播域,VLAN内的主机间可以直接通信,而VLAN间则不能直接互通。这样,广播报文就被限制在一个VLAN内。

具体流程如下:

1. 光猫 IPTV 端口带上 VLAN 标签(tag)

我的目标是让 IPTV 端口出来的数据全部带上 VLAN 标签接入局域网,进而也因为 tag 标签避免对其他设备影响。
光猫一般用户是无权配置 VLAN 的,因此找宽带供应商工程师拿到光猫超级管理员账号(telecomadmin)和密码。在 网络 —— 网络设置 —— VLAN绑定 中新增配置,指定 iTV 端口的 3_OTHER_B_VID_43( WAN 侧服务名)绑定用户侧 VLAN,VLAN ID 我改为43。

配置过后,光猫 IPTV 出来的原始数据都会带上 VLAN 43 tag 的标签,也会接受相同 tag 的数据帧,并在内部去除 tag。
将光猫 IPTV 端口接入到软路由中,这样弱电箱到客厅单根网线上,就同时有 正常Internet + IPTV(tag) 两种数据。

2. 网管交换机指定端口解 VLAN 标签(untag)

接下来目标是客厅的交换机指定端口解 VLAN 标签,让 IPTV 数据走该端口,其他数据摒弃。

交换机物理上1口是接入单线复用,4口上连接机顶盒。进入交换机后台,创建一个名为 IPTV 的 VLAN 配置,VLAN ID 为43:

  • 4口选择“不带标签”,这样4口发出的数据帧是具有标签的将会去除标签,进来的数据将会在交换机内部加上标签;
  • 1口选择“带标签”,交换机中属于该 VLAN 的数据发出1口时会带上标签,其他正常数据也会流转;
  • 其他端口选择“非成员”,相当于不敢感知 VLAN 数据。

同时配置 VLAN PVID,让4口仅接受来自机顶盒未打标签的数据帧,当未打标签的数据进入交换机时,将会以 PVID(43) 打上标签,避免机顶盒可能发出 VLAN 数据对链路的影响。

如上两步配置,实现基于 VLAN 的单线复用。

成都电信光猫盒子(天邑808AE)破解超级密码记录

最近换了工作地点,新租房的房东默认配置了电信宽带,附带上宽带盒子,这个盒子算是一个多功能盒子,包括以下功能:

  • 光猫
  • 电视盒子(IPTV安卓4.4系统 + 带红外遥控 + 视频/音频HDMI接口 + 音频SPDIF接口)
  • 路由(一个千兆网口 + 一个百兆网口)
  • 2.4G WIFI(带WPS)
  • 电话(RJ11接口)

还带两个USB2.0接口和SD卡接口,但侧边的USB和SD卡接口都是给IPTV安卓系统使用,只有后面的USB接口可以拿来做存储使用,该口也用作后面的破解密码使用。


进入正题,接下来介绍具体的过程:

1. 获取管理界面

一般运营商所附赠的盒子都会在背面标注配置地址,常为192.168.1.1,也会标注上配置账号和密码(下称useradmin账户),访问网页结果如下,目前该设备的账号名为useradmin。

使用给定的useradmin账户登录,查看设备详情,如下图。如果是相同或者相近的产品型号,可以根据我的思路进行破解。

需要注意,该界面,基本只能用于修改修改wifi配置信息,该配置地址192.168.1.1也是一个非运维地址,真正运维地址是192.168.1.1:8080,该网页访问情况如下:

目前,可以使用useradmin账户登录该管理界面,但也存在权限问题。虽然相较于之前的页面多了一些配置内容,但基本都是些无关痛痒的操作,并不能实现对宽带拨号、桥接、IPV6等的修改。要实现完全功能的修改,需要使用真正给运维人员的账户(下称telecomadmin账户),接下来,我们将使用useradmin账户+运维地址192.168.1.1:8080来获取telecomadmin账户。

2. 利用小trick拿到备份文件

在广大网友的智慧下,使用useradmin账户登录运维地址,然后打开开发者界面(快捷键F12),切换到网络面板。然后网页访问:管理→设备管理,在网络请求中找到:MD_Device_user.html,见下图:

该HTML源码中有个函数enableClick(),作用是实现将盒子配置内容备份到USB存储设备中。但在网页中并没有提供触发这个函数的操作(比如点击某个按钮),需要我们手动触发。

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
//USB 快速恢复 启用/禁用
function enableClick() {
var loc = 'usbbackup.cmd?action=backupeble';

with ( document.forms[0] ) {
if ( enable[0].checked == true ) {
if(list.length > 1) {
btnDown.disabled = true;
}

loc += '&enabled=1';
} else if ( enable[1].checked == true ) {
if(list.length > 1) {
btnDown.disabled = false;
}

loc += '&enabled=0';
}
}

loc += '&sessionKey=' + sessionKey;

var code = 'location="' + loc + '"';
eval(code);
}

分析该函数,发现只需要拼接一个字串,就可以访问该功能。其中enabled代表是否启用USB快速恢复功能,当然此处需要启用。最后拼接如下:

1
loc = usbbackup.cmd?action=backupeble&enabled=1&sessionKey=1795138985

这里的sessionKey来源在源码的上方

1
2
3
4
5
6
7
var obj1Items = '1|7|0';
var usblist = '';
var list = usblist.split("|");
var sessionKey = '1795138985';
/*var obj1Cols = numOfCol(obj1Items);*/
/* MDMOID_RESTORE is Type0 object, follow variables are single value */
var usbbackenable = getValueFromList(obj1Items, 0);

给出了 sessionKey 的值,复制过来,拼接成一个完整的字串。

懂URL编码的同学可以发现,该字串就是http请求url的一部分,其中的 enabled 和 sessionKey 都是GET请求的 params

然后补上前面的地址部分,形成一个完整的访问网址:

192.168.1.1:8080/usbbackup.cmd?action=backupeble&enabled=1&sessionKey=1795138985

一定要创建一个新页面,然后打开开发者工具,去访问该网页。如果顺利,会得到一个这样的网页:

但是由于 sessionKey 有访问时间、次数限制,如果短时间没有进行操作或者重复操作,会访问失败,得到如下结果。

如遇到这样的情况,请重新访问:管理→设备管理,拿到新的 sessionKey 来访问该网页。下图是是正常的访问页面。

在某些设备中,除上图中的三个按钮(恢复出厂设置、重启、保存设置)外,还有有一个备份配置的按钮,如下:

  • 注:该图来源于网络,我的设备没有备份配置按钮

如果你有备份配置按钮,可以提前插入U盘(最好格式化为Fat32格式),然后点击备份配置,得到一个 cfg 类型文件,跳过之后的步骤到第3步。

如果你遇到我这样没有备份配置按钮的情况,那么😒,还需要绕一圈。我们继续使用开发者工具,查看该界面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
var product_type = '3'; //added by hlb,2014.04.21
...
if(product_type == '4')
{
document.write("<div>");
document.write("<b id='Title_usb_backup_lable'>USB备份配置:</b>");
document.write("<div style='text-align:center'>");
document.write("<p id='Title_usb_fast_backup_lable'>快速恢复:&nbsp;&nbsp;&nbsp;");
document.write("<label id='RecEnable_checkbox'><input type='radio' name='enable' onClick='enableClick()' />启用</label>");
//...
document.write("<input type='button' name='btnDown' onClick='btnApply()' value=' 备份配置 ' />");
document.write("</p>");
document.write("</div>");
document.write("</div>");
}

发现product_type还做了限制,必须product_type=4的设备才能有该功能。不过我们可以继续梳理逻辑,如果存在该功能,当点击备份配置按钮的时候,会访问到*btnApply()*函数,继续找到该函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function btnApply() {
var loc = 'usbbackup.cmd?action=backup';
with ( document.forms[0] ) {
if ( usbsubarea.value == "" || usbsubarea.value == "none" ) {
msg = '请插入USB设备';
alert(msg);
return;
}
//usbsubarea queueintf
idx = usbsubarea.selectedIndex;
val = usbsubarea.options[idx].value;
loc += '&subarea=' + val;
loc += '&sessionKey=' + sessionKey;
}
var code = 'location.assign("' + loc + '")';
eval(code);
}

又是一个拼接函数,继续按照逻辑拼接,得到如下的结果:

1
loc = usbbackup.cmd?action=backup&subarea=usb1_1&sessionKey=1222473631

其中的 subarea 值和 sessionKey 的值可以在该页面的上方找到:

1
2
3
4
var obj1Items = '1|7|0';
var usblist = 'usb1_1|';
var list = usblist.split("|");
var sessionKey = '781652989';
  • 注:若usblist 值为空串, 请插上U盘重试,建议U盘格式化为Fat32格式。

保证设备后面插上U盘,并形成一个完整的链接:

192.168.1.1:8080/usbbackup.cmd?action=backup&subarea=usb1_1&sessionKey=781652989

  • 注:subarea 字段值为 sublist 以|隔断的第一个字串。

访问该链接,如果顺利,不会有访问异常,而且在U盘目录下新生成有如下cfg类型文件:

如果没有该文件,请尝试重试以上步骤🙄,尤其是 sessionKey 的获取有效。

3. 使用routerpassview 获取超级密码

如果拿到cfg文件,在网络上搜索下载routerpassview软件,并用该软件打开U盘内的cfg文件。如果软件被杀毒软件拦截,请酌情放行。

在软件中查找telecom关键词(快捷键Ctrl + F),如果能找到如图的内容,恭喜你破解telecomadmin账户成功👍。账号名为telecomadmin,账户密码为图中Password中间标识的字段,该密码跟随设备,相同设备间一般不一样,甚至同一设备恢复出厂设置后都有可能会修改。

最后,使用telcomadmin账号登录运维地址

附上 useradmin 账号和 telcomadmin 账号登录运维地址的截图,可以看到多出来的配置功能。

telecomadmin账号登录运维截图

useradmin账号登录运维截图

“中华人民共和国公民身份号码” 验证算法

中华人民共和国公民身份号码由18位组成,其中前17位由阿拉伯数字0~9组成,最后一位由0~9或X 构成,按构造顺序如下:

位置 长度 说明
区域码 1~6位 6 指公民常住户口所在地的行政划区代码,由省(2位)+市(2位)+区(县)(2位)构成
出生日期码 7~14位 8 指公民出生的公历日期,由年(4位)+月(2位)+日(2位)构成
顺序码 15、16位 2 表示在同一区域码所标识的区域范围内,对同年、同月、同日出生的人编定的顺序号
性别码 17位 1 奇数代表男性,偶数代表女性
校验码 18位 1 通过前17位计算所得,见国标GB11643-1999

根据标准,最后一位校验码采用ISO 7064:1983.MOD 11-2校验码计算法,具体计算方法解释如下,令$N_i (i=1,2…16,17)$表示每一位身份证号,需要首先计算各位的权重$W_i$:
$$ W_i = 2^{17-(i-1)} \mod 11 $$
计算所得权重数据如下表:

位数$i$ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
权重$W_i$ 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2

最后一位校验码 $C$ 由下式计算而出:
$$ C= (12 - (\sum_{i=1}^{17}{(N_i W_i)} \mod 11 ) \mod 11 $$
可以发现,$C$因为最后与$11$求余计算,导致$C$必然小于等于$10$。又因为阿拉伯数字限制,如果$C=10$,则校验码为字符"X";如果$C \leq 10$,则校验码为数字"C"。

我们可以根据计算前17位身份证码所得校验码与所提供的第18位校验码进行是否相等判定,从而得出所输入的18位身份证号是否有误。这也是一些网站初步验证身份证号的机制。

附上本人所写的一个提取身份号码有关信息的小工具——“中华人民共和国公民身份号码”验证工具

动态规划之背包问题

背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。

也可以将背包问题描述为决定性问题,即在总重量不超过$W$的前提下,总价值是否能达到$V$。

定义

我们有$n$种物品,物品$i$的重量为$w_i$,价值为$v_i$

我们假定所有物品的重量和价值都是非负数,背包所能承受的最大重量为$W$。

  • 如果限定每种物品最多能选择1个,则该背包称为01背包。

    可以用公式表示为:

$$ \begin{align} 最大化 & \quad \sum^{n}_{i=1}{v_{i} x_i} \\ 受限于 & \quad \sum^{n}_{i=1}{w_{i} x_i} \leq W, \quad x_i \in \{0,1\} \\ \end{align} $$

  • 如果限定物品$i$最多只能选择$b_i$个,则问题称为多重背包问题(有界背包问题)。

    可以用公式表示为:
    $$ \begin{align} 最大化 & \quad \sum^{n}_{i=1}{v_{j} x_i} \\ 受限于 & \quad \sum^{n}_{i=1}{w_{i} x_i} \leq W, \quad x_i \in \{0,1…,b_i \} \\ \end{align} $$

  • 如果不限定每种物品的数量,则问题称为完全背包问题(无界背包问题)。

  • 各类复杂的背包问题总可以变换为简单的0-1背包问题进行求解。

01背包

设一共有$N$件物品,第$i$件物品的重量为$w[i]$,价值为$v[i]$,最大总重量为$W$。

如果采取暴力枚举方法,每件物品都存在装入和不装入两种情况,所以时间复杂度为$O(2^n)$,使用动态规划可以把时间复杂度控制在$O(N*W)$。求解的总目标是背包物品的总价值,变量有是否放入物品和书包限重。

定义$dp[i][j]$表示将前$i$件物品放入限重为$j$的书包可以获得的最大价值,其中$0 \leq i \leq N, \ 0 \leq j \leq \ W$。

状态转移:

  1. 不装入第$i$物品:$dp[i][j] = dp[i-1][j]$
  2. 在能装入的前提下,装入第$i$件物品:$dp[i][j]=dp[i-1][j-w[i]]+v[i]$

即状态转移方程为:

1
dp[i][j] = max(dp[i-1][j], j-w[i] >= 0 ? dp[i-1][j-w[i]]+v[i] : 0)

由状态方程可知,dp[i][j]的值只与dp[i-1][0,…,j-1]有关,所以可以采用滚动数组对空间优化。需要注意的是,为了防止上一层循环dp[0,…,j-1]被覆盖,需要逆向枚举$j$(空间优化前没有这个限制)。代码如下:

1
2
3
4
5
6
7
8
9
10
int solution01Knapsack(int N, int W, std::vector<int> &w, std::vector<int> &v) {
// w[i]重量 v[i]价值
std::vector<int> dp(W+1);
for (auto i = 0; i < N; i++) {
for (auto j = W; j >= w[i]; j--) {
dp[j] = std::max(dp[j], dp[j - w[i]] + v[i]);
}
}
return dp[W];
}

时间复杂度为$O(NW)$,空间复杂度为$O(W)$。第$i$件物品装入或者不装入而获得的最大价值完全可以由前面$i-1$件物品的最大价值决定,暴力枚举忽略了这个事实。

完全背包

设一共有$N$件物品,第$i$件物品的重量为$w[i]$,价值为$v[i]$,每件可以无限多个,最大总重量为$W$。

与01背包非常相似,状态dp定义一致,只是状态转移稍微不同:

  1. 不装入第$i$物品:$dp[i][j] = dp[i-1][j]$(同01背包)
  2. 在能装入的前提下,因为每件物品有无限个,所以不从$dp[i-1][j-w[i]]$转移,而是从$dp[i][j-w[i]]$转移,即装入第$i$种商品后还可以再继续装入该种商品。即装入第$i$件物品:$dp[i][j]=dp[i][j-w[i]]+v[i]$

状态转移方程为:

1
dp[i][j] = max(dp[i-1][j], j-w[i] >= 0 ? dp[i][j-w[i]]+v[i] : 0)

和01背包问题相似,也可以进行空间优化,不同点在于完全背包的$j$只能正向枚举而01背包只能逆向枚举,因为$max$第二项是dp[i]而01背包是dp[i-1],即完全背包需要覆盖而01背包需要避免覆盖。代码如下:

1
2
3
4
5
6
7
8
9
10
int solutionUnboundedKnapsack(int N, int W, std::vector<int> &w, std::vector<int> &v) {
// w[i]重量 v[i]价值
std::vector<int> dp(W+1);
for (auto i = 0; i < N; i++) {
for (auto j = w[i]; j <= W; j++) {
dp[j] = std::max(dp[j], dp[j - w[i]] + v[i]);
}
}
return dp[W];
}

完全背包问题也可以转化为01背包问题来解:将一种物品转换成最多能放下的若干件。

最简单的想法是,考虑到第$i$种物品最多装入$W/w[i]$件,于是把第$i$种物品转化为 $W/w[i]$件费用及价值不变的物品,然后求解这个01背包问题。时间复杂度为:$O(NW\frac{\sum{\frac{W}{w_i}}}{N}) = O(W\sum{\frac{W}{w_i}})$ 。

更高效的转化方法是采用二进制的思想:把第$i$种物品拆成重量为$w_i 2^k$、价值为$v_i 2^k$若干件物品,其中 k 取遍满足$w_i 2^k \leq W$的非负整数。这是因为不管最优策略选几件第$i$种物品,总可以表示成若干个刚才这些物品的和(例:$13 = 2^0 + 2^2 + 2^3$)。这样就将转换后的物品数目降成了对数级别。时间复杂度为:$O(NW\frac{\sum\log_2{\frac{W}{w_i}}}{N}) = O(W\sum\log_2{\frac{W}{w_i}})$。

多重背包

设一共有$N$件物品,第$i$件物品的重量为$w[i]$,价值为$v[i]$,数量限制为$n[i]$,最大总重量为$W$。

参考完全背包转为01背包的思想,$k$表示装入第$i$种物品的件数,k <= min(n[i],j/w[i])​

1
dp[i][j] = max{(dp[i-1][j-k*w[i]]+k*v[i]) for range(k)}

同理也可以进行空间优化,而且$j$也必须逆向枚举:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int solutionBoundedKnapsack(int N, int W,
std::vector<int> &w,
std::vector<int> &v,
std::vector<int> &n) {
// w[i]重量 v[i]价值
std::vector<int> dp(W + 1);
for (auto i = 0; i < N; i++) {
for (auto j = W; j >= w[i]; j--) {
for (auto k = 1; k <= std::min(n[i], j / w[i]); k++) {
dp[j] = std::max(dp[j], dp[j - k * w[i]] + k * v[i]);
}
}
}
return dp[W];
}

时间复杂度为:$O(NW\bar n) = O(W\sum{n_i})$,空间复杂度为:$O(W)$。

当然也可以采用二进制思路,将第$i$种物品分成了$log_2{n_i}$件物品,将时间复杂度优化为:$O(W\sum{\log_2{n_i}})$。

杂项

背包问题还有一些其他变种问题。

恰好装满

要求:必须恰好装满背包。

只需要修改初始化,将$dp[0,…,N][0]$初始为0,其它$dp$值均初始化为-inf,因为此时只有容量为0的背包可以在什么也不装情况下被“恰好装满”,其它容量的背包初始均没有合法的解,应该被初始化为-inf。

总方案数

要求:求背包装满或则装至某一指定容量的总方案数。

修改转移方程中的$max$为$sum$。

二维背包

上文讨论的背包都只有一个限制:最大容量。二维背包指一个背包存在两个限制(比如重量和体积限制)。

此类问题的解法和一维背包相似,需要$dp$数组要多开一维,其他和一维背包一致。

输出最优方案

要求:输出背包问题的解方案。

可以参照一般动态规划的方案输出方法:记录每一个状态是由那条策略推导而出,以便回溯出上一个状态。

以01背包为例,再开辟一个数组$P[i][j]$来记录方案,$P[i][j]=0$表示$dp[i][j]$采用第一种状态转移策略,即$dp[i][j] = dp[i-1][j]$,$P[i][j]=1$表示采用第二种状态转移策略,即$dp[i][j]=dp[i-1][j-w[i]]+v[i]$,从而反推方案。

另外我们也可以从求出的$dp$数组进行反推,若 $dp[i][j] = dp[i−1][j]$ 说明未选第$i$个物品,反之说明选了。

0%