Fari

计算机网络

深圳联通光猫改桥接访问ipv6

问题

深圳联通宽带,提供了ipv6地址,内外可以访问,外网仍然访问不了。经排查,问题可能出在联通光猫上,它带有ipv6的防火墙,会阻拦入站流量,没有找到关闭的方法。

遂想到是否可以将光猫修改为桥接模式,然后使用路由器进行PPPoE拨号上网,自己的路由器对ipv6应该更宽容。

想要修改光猫就得拥有光猫的管理员权限,问题在于,由于联通宽带光猫的管理员账号密码是动态变化的,问宽带师傅他还不一定给。说来也比较奇怪,一个月前我在网上搜了一下,广东的联通光猫账号密码都是 CUAdmin/cuadmin+光猫mac前6位(光猫后面贴的有),当时还能登录进去,现在又登录不了了,不知道是不是最近才改成动态密码的(每过一段时间宽带提供方会自动更新光猫的密码)。

网上有很多通过telnet和ftp的方式查看密码的方法,例如这个等等,但到我这都没用,我按照他们提供的配置页面地址进去,全都会跳到登录页面,即使我已经登录了普通账户(光猫后面都会贴上一个普通用户的账号和密码)

后来是通过重置光猫的方式解决管理员账户的问题的,注意,在重置之前有些必要信息需要提前保存,以下为详细过程

光猫改桥接

记录信息

首先进入光猫后台(一般是 192.168.1.1),登录普通用户(光猫后面有贴)

登录后选择 “认证注册” 选项,将LOID(逻辑id)复制并保存在本地,后面要用

再进入 “网络” 选项,查看PPPoE拨号的账号和密码,账号一般明文显示,密码则是星号显示,要查看密码也很简单,打开浏览器的开发者模式,定位到密码框,将 input 标签的类型由 password 改为 text 即可。我这边查看到的密码就是账号的后6位。将账号和密码保持在本地

进入 “状态” 选项,记录一下vlanid,这一步对我来说没什么用,但是记录一下以防后续用到。vlanid就是后面的那几个数字

注:简单解释一下这三个东西是什么,可以直接理解成运营商会给你的光猫分配三条宽带,它们每一条宽带都是独立的,且拥有独立的ip地址:

重置光猫

拿一根牙签一直按住光猫后面的复位按钮,直到前面板的等只剩一个电源灯在亮

设置光猫

再次进入光猫后台(192.168.1.1),选择管理员登录,此时直接使用 CUAdmin/CUAdmin 登录即可

进入 “高级配置” 选项,选择 LOID配置,在 逻辑ID 处填入刚保存的LOID,点击保持按钮。

对我来说,到这一步之后,稍等一会我的光猫就会在路由模式下自动连上网,并且管理员密码已经被修改了,也就是说,光猫能不能上网貌似就认这个 LOID,至于拨号上网的PPPoE账号和密码则是自动下发的。

若想让其变为桥接,关键一步来了,你需要在点击保持上述LOID后,稍等一会(半分钟?一分钟?见下面的注)然后立即进入 “快速配置” 选项,选择第一个桥接方式,然后点击下一步,后面还会询问是否需要启用光猫的无线网,去掉勾勾即可,再点击完成

注:快速配置这里默认是勾选的桥接方式,当设置了 LOID 后,并且LOID生效后,它又会变成默认勾选路由方式,所以这里等待的关键就是要等它自动勾选为路由方式之后,再手动选择桥接方式

配置路由器

我这个路由器也是办宽带送的一个小米路由器,进入小米路由器后台(miwifi.com),在常用设置中找到上网设置,默认这里是DHCP方式,将其修改为PPPoE方式并输入刚才保存的宽带账号密码,点击应用按钮,等待一会即可

当在当前页面中的上网信息中能看到外网的ip地址时,说明已经可以了

注:路由器修改为PPPoE后就无法通过192.168.1.1进入光猫后台了

IPv6

路由器设置

在路由器后台的 上网设置 中找到 ipv6,将上网方式改为 Native

路由过程

基本过程

数据包从主机1.1.1.1发送到目标主机5.5.5.5的过程

首先从初始网关(一个路由器)中查找路由表,路由表形式如下(windows下使用 route print 查看):

注:网关中的“on-link”表示该目标网络和该主机存在同一个子网下,不需要经过路由转发

根据前两列(目标网络和网络掩码)确认目标主机应该从以哪个接口(第四列,下面有解释)发送给哪个网关(第三列,即路由的下一跳地址)

路由表的第一行为默认网关,如果在下面的路由表中找不到对应目标主机的网络记录,则发送给默认网关,后续的路由过程也是如此

例如一般本机的路由表中都只会记录局域网的路由记录,如果是发送数据到公网或其他局域网的主机,一般都是走默认网关,可以使用 tracert destination-ip 查看路由过程(一般默认网关ip都是一1结尾,下图中可以看到先走了两次默认网关)

如果有多个匹配的目标网络地址,则根据最优匹配规则(最长前缀等)及路由表的Metric列(数值越小,路径越短)选择最合适的网关进行发送

当然,网关只是一个ip地址,属于传输层,它还需要根据ip地址找到mac地址才能进行发送,这里就是arp协议的功能了

网关 vs 接口

一台路由器或主机可能通过交换机连接着多台路由器,每个路由器都可以看作是下一跳的备选地址,具体选择哪一个就是根据目标网络和网络掩码决定。在路由表上的体现就是根据前两列找到某一行,第三列网关就是下一跳路由器的地址。

一台主机可能有多张网卡,所以就会有多个ip地址,例如,你本机可能和校园网在一个子网,则校园网会给你分配一个ip。你在主机上使用VMware使用“仅主机”的网络模式开启了其他的虚拟机,则你的主机又和这些虚拟机组成一个局域网,它又会给你分配一个ip。如此你的主机就会有两个ip,路由表中的接口作用就是说,数据包在发送给网关时,选择使用哪个ip。

路由器 vs 交换机

交换机:有6台主机要相互通信,则需要使用 6*5=30 根网线将它们连接起来,但是如果用交换机的话,则每台主机都只用连在交换机上,当两台主机要进行通信时,交换机负责将它们连接起来。这样就只需要6根网线。交换机的数据转发是基于mac地址的,它会维护一个mac地址与端口的映射表。

路由器:同一个子网之间通信不需要路由器,只用交换机就行了,路由器只有在数据包发送给其他局域网时才会有作用,路由器一般有两个或多个网络接口,分别连接多个子网,找到数据包下一跳接口的过程就是路由。

若一个网络没有路由器而只有交换机,主机之间也是可以通过ip进行访问的,例如某台主机具有dhcp的功能,则其他主机会先广播找到dhcp服务器的mac地址,然后将自己的mac地址发送给dhcp服务器,它就会返回一个ip。或者也可以手动配置静态ip等

同服务器一样,你应该将路由器看作是一个软件而不是硬件,任何主机都可以做路由器,就像任何主机都可以做服务器一样,只不过主机上安装的软件不同,我们对它的叫法也就不同

网关 vs 路由器

网关只是一个统称,所有流量需要经过的地方就是网关,路由过程中,下一跳的路由器就是一个网关。但在路由表中,网关又被具体化成下一跳的路由地址。

Q&A

  1. 如果路由过程中,某路由器存在多个符合条件的路由记录,它们的优先级相同,数据包会如何发送?
    数据包被复制成多份并同时发送给这些符合条件的网关路由,由于它们具有相同的目标网络地址,则当它们到达目标网络的路由器时,路由器会自动去重,保证只有一个数据包发送给目标主机
    自动去重的一种解决方案:网络设备维护一个长度为100个数据包缓存的队列,当新的数据包到来时,其和队列中的数据包进行hash比对,若存在则将其丢弃,不存在则弹出队列头部的数据包,并将新数据包添加到队尾

  2. 路由表如何更新?
    路由表可以手动修改(静态路由)也能自动更新(动态路由)。它使用动态路由协议来自动更新路由表,例如rip是一种动态路由协议,它规定每过30秒就和相邻的路由器同步路由表。当同步次数足够多时,所有的路由表都会收敛成定值(排除网络环境的变化)。

  3. 路由表会不会无限增大?
    不会,它可以根据一定策略更新本地路由表,而不是盲目地把所有拉取的记录都保存下来,例如跳数过多的记录就不会被保存
    第1个路由器拉取第2个路由器的路由表,第2个路由器拉取第1个和第3个路由器的路由表,如此持续下去并且不停迭代,若不加以限制,所有的路由器中的路由表都会变成一样的(每个路由器的网关还是不同的,因为单个路由器的路由表中的网关只能是其相邻路由器的地址,所以即便第1个路由器最终会有第99个路由器的网络记录,但该记录的网关还是第2个或其他相邻路由器的地址,但此时跳数为98),但跳数是不一样的,故如果跳数过大,则不进行记录

  4. 路由表会不会形成环路
    会,但可以尽量避免,即使形成了环路,也会有一定的检查和修改措施,这些都是自动完成的

VMware中桥接、仅主机、NAT三个模式的简单理解

在VMware菜单栏中,依次选择 编辑 > 虚拟网络编辑器 > 更改设置(需管理员权限)可以看到VMware中存在的一些网络,其中分为三大类:桥接、仅主机、NAT file

桥接模式

最常用的模式,VMware虚拟出一个交换机,将所有处于该网络下的虚拟机都通过该交换机连接到宿主机所在的网络中,此时,所有的虚拟机与宿主机在外部真实路由器网络层面是相等的。 file

仅主机模式

使用虚拟路由器创建出一个隔离的虚拟网络,所有处于该网络的设备之间可以通信,但不能同外界通信(宿主机除外,可以理解为它装了两块网卡,一块连接虚拟网络,一块连接外部路由器,所以两边都能通信),所有的虚拟机与宿主机在内部虚拟路由器网络层面是相等的。相当于国家的一些军用网络(很多军用网络都是独立组网,不与任何外界相连的) file

NAT模式

在仅主机模式的基础上,将虚拟路由器同外部网络进行连接,形成一个NAT网络,此时虚拟机就可以同外部网络进行通信,相当于校园内部的局域网通过路由器连接到公网上了 file

tcp连接

连接状态

tcp本身就是长连接+全双工的,什么意思呢?就是tcp连接一旦建立,就会一直维持连接状态,这个状态下,服务端和客户端是可以同时发送和接收数据的。注意是同时,也就是说发送的同时也能接收,它们是两块独立的缓冲区。下面是python代码,服务端和客户端建立连接后,就启用两个线程,一个用于读数据,一个用于发数据。

# server.pyimport socket,timeimport threadinghost = '127.0.0.1'port = 12345def read(c):    while True:        data = c.recv(1024)        print(f"server recv: {data.decode('utf-8')}")def write(c):    while True:        v = input()        c.sendall(v.encode('utf-8'))s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.bind((host, port))s.listen()print("server start...")conn, addr = s.accept()print(f"client connect, {addr}")threading.Thread(target=read, args=(conn,)).start()threading.Thread(target=write, args=(conn,)).start()time.sleep(9999)conn.close()s.close()
# client.pyimport socketimport threadingimport timehost = '127.0.0.1'port = 12345s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect((host, port))threading.Thread(target=read, args=(s,)).start()  # 同server的read()方法threading.Thread(target=write, args=(s,)).start()time.sleep(9999)s.close()

几个点需要注意:

1. 在连接没有断开之前(代码中的sleep(9999)作用就是阻止调用close()),两端可以任意发送和接收数据,发送多少次都可以,这也是目前长连接或者websocket的实现基础。

2. 默认情况下,recv()函数在连接建立的状态下是阻塞的,也就是说,如果对方没有向写recv缓冲区放数据的话,该方法会一直阻塞

3. 连接断开的情况下,recv()函数就会立刻返回空字符串。这里又有三点需要注意:

  1. 连接断开,指的是对方不管正常还是异常退出。

  2. 立即返回空字符串,即“”,而不是None

  3. 连接断开后,数据接收方的recv()是会一直接收一个空串的,此时已经不阻塞了,如果recv()在循环体内,则会一直循环

下图展示了客户端正常退出后,两者的tcp状态。tcp规定发起退出的一方会在最后进入CLOSE_WAIT状态,以等待该连接彻底失效。而此时,由于我上面的代码并没有判断正常退出的流程,所以理论上它们两者的状态会一直维持现状。

解释一下这两个状态产生的过程:客户端要关闭连接了,给服务端发送了一个关闭tcp连接的报文(FIN),此时客户端会进入FIN_WAIT1状态等待服务端确认,服务端收到报文后会自动回复一个ACK,此时服务端进入CLOSE_WAIT状态,而客户端收到该ACK后就进入FIN_WAIT2状态,这两个状态是相辅相成的,其本质是等待服务端将剩下的数据传输完成,注意这些步骤都是tcp完成的,而不是我们代码实现。实际上tcp并不可能知道到底什么时候服务端会将数据传输完成,所以它会一直等待业务逻辑代码执行完,直到调用close()方法,而我上述代码中,它会sleep很久,所以这个状态会一直保持。理论上tcp协议会将这个状态保持到系统关机,实际上系统也会设定一个超时时间。

所以正常情况下,当调用recv()返回空字符串时,你就应该意识到对方已经关闭了,所以你这边也应该调用close()方法了。

那么如果我就是给对方发送了一个空字符串呢?这是不被允许的,空字符串是发不出去的,必须有内容。

当然,也可以设置recv()不阻塞,s.setblocking(False),这种情况下,无论如何recv()都会立即返回,但是如果没有数据的话,就会报异常“BlockingIOError: [ Errno 11 ] Resource temporarily unavailable”。此时可以使用try对其封装,或者使用select模块,这就是非阻塞io或NIO的实现方式了。