开发专栏
使用 Razor Pages 打造查看真实 IP、IPv6 检测网站 —— 思路篇
4 年前
7276
服务器端开发 ASP.NET 树莓派 IPv6 Razor Pages

随着IPv6的普及和.NET5的到来,用Razor Pages做一个查看真实IP地址、IPv6可用性检测的网站如何?本文是系列文章的第一篇—设计思路。

使用 Razor Pages 打造查看真实 IP、IPv6 检测网站系列文章:

碎碎念:上个月居然没写博客,没能把每个月至少一篇坚持下来😭这个月就多发点吧,吼吼吼~

背景

去年(2019年) IPv4 终于耗尽了,终于不用每隔一段时间就看到一次 IPv4 地址即将耗尽的新闻了🙄……

近几年越来越多的 app 启动画面带上了 IPv6 的标志,表示他们开始支持 IPv6 接入。

支持ipv6的服务商

移动运营商也已经纷纷支持 IPv6,2019 年宣布 IPv4 耗尽那天,我试着用手机网络(辽宁联通)访问了 http://test-ipv6.com/ 发现手机移动网络已经分配到了 IPv6 地址,我还留有当时的截图:

手机网络可以获取到IPv6

不过当时家里的联通宽带是光猫拨号,公网 IP 分配给了光猫,路由器和网络里其他设备都是内网 IP,并且光猫似乎不支持 IPv6 下发,内网设备都没有分配到 IPv6 地址。进入光猫后台,用网上那些管理员密码等方式也无法连接、修改这个光猫的配置。给联通打电话要求改为路由器拨号的模式,装宽带的小哥推来推去,就是没让改,只能作罢。

然后时间就到了今年(2020年)8月,想把家里闲置的树莓派利用起来,于是想先做个 Web 站点吧,正好利用一下 IPv6 公网地址可以直接访问的特性,也省得内网穿透来穿透去了。于是又给联通打电话,10010 客服娴熟的让我报了个故障让装宽带小哥联系我,结果和去年还是同一个小哥打来电话,心里凉了一半,不过这次小哥很爽快的让我改成了路由器拨号……可能一年过去了,小哥业务水平得到了精进~于是,家里终于覆盖了 IPv6。

好,现在来访问一下 http://test-ipv6.com/ 网站,如果它显示出了“你的公网 IPv6 地址是 XXXX”,则说明你的网络支持 IPv6,否则说明不支持。

这期博客的内容就是要通过 ASP.NET Razor Page 来山寨一个能够显示访问者真实 IPv4 和 IPv6 的站点~如果能显示出 IPv6 地址就说明访问者的网络支持 IPv6。我的山寨版本在这里 https://ip.bun.plus/

出于简单起见,本文主要是介绍 IPv6 和 Razor Page,显示样式不是重点,最终效果大概是这样:

访问效果

当然,这两个版本的源代码都可以在我的 GitHub 上找到:

为了完成山寨,我们会用到:

  • IPv6 地址
    • 如果想把最终网站发布,还需要有 IPv6 地址
    • 如何为 IPv6 地址配置 nginx
  • ASP.NET Razor Page
    • 使用页面 Page 和模型 Page Model
    • 使用 Razor Page 创建 Web API
    • 跨域请求配置
    • 使用视图组件 View Component
    • 配置 appsettings.json
    • 如何绑定多个地址进行调试
  • 树莓派
    • 在树莓派上安装 .NET 5 运行时
    • 通过 C# 获取树莓派型号

这期文章内容实在太多,挣扎再三决定分为上中下三篇:

  • 本篇为上篇,简单介绍 IPv6 和我们网站的设计思路
  • 中篇介绍核心的获取 IP 的编码部分以及 nginx 的配置
  • 下篇介绍 ASP.NET Core 视图组件的使用以及树莓派的专属彩蛋

简单了解 IPv6

在 Windows 我们可以通过 ipconfig 命令来查看当前的 IP 地址:

PS C:\Windows\System32> ipconfig

Windows IP 配置

以太网适配器 以太网:

   连接特定的 DNS 后缀 . . . . . . . :
   IPv6 地址 . . . . . . . . . . . . : 2408:832f:22a1:4310:29b5:1c68:38d9:a5a9
   临时 IPv6 地址. . . . . . . . . . : 2408:832f:22a1:4310:9d6c:92ee:f2cd:a1c8
   本地链接 IPv6 地址. . . . . . . . : fe80::29b5:1c68:38d9:a5a9%18
   IPv4 地址 . . . . . . . . . . . . : 10.0.0.150
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   默认网关. . . . . . . . . . . . . : fe80::5aef:68ff:fe7b:3830%18
                                       10.0.0.1

Linux 通过 ifconfig 查看:

pi@bunpi:~ $ ifconfig

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.0.0.20  netmask 255.255.255.0  broadcast 10.0.0.255
        inet6 fe80::30da:fd9b:c9e7:9ef9  prefixlen 64  scopeid 0x20<link>
        inet6 2408:832f:22a0:56d0:a754:b831:3299:8a50  prefixlen 64  scopeid 0x0<global>
        ether b8:27:eb:4c:22:fc  txqueuelen 1000  (Ethernet)
        RX packets 17579  bytes 5622398 (5.3 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2331  bytes 216594 (211.5 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

如果能看到诸如 2408:832f:22a1:4310:9d6c:92ee:f2cd:a1c8 这种包含冒号 : 的地址,说明你的电脑已经得到了 IPv6 地址。不过 IPv6 和 IPv4 一样,如果是内网地址则只能在内网中访问,无法通过公网访问:例如以 fe80 开头的地址就不能通过公网访问。

你可能会发现,test-ipv6 网站显示的 IPv6 地址和 IPv6 地址 后面的值不一样,这是因为 Windows 系统默认会获取到两个 IPv6 地址,对外通信时使用的是 临时 IPv6 地址,所以 test-ipv6 只能得到临时 IPv6 地址。

另外我们熟悉的 IPv4 地址 127.0.0.1 在 IPv6 中为 0000:0000:0000:0000:0000:0000:0000:0001,但是这么多 0 写起来很累也不好阅读,可以用两个冒号 :: 省略连续的 0,所以可以缩写为 ::1

最后就是 ipconfig 的输出结果,有些 IPv6 地址后面带有 %18 这样的后缀,它表示网络接口(网卡)标识,可以用 route print 命令查看到,我这里的 %18 表示 Intel(R) Ethernet Connection (2) I219-V 这个网卡。

C:\Windows\System32> route print
===========================================================================
接口列表
  5...00 15 5d cc 6f f7 ......Hyper-V Virtual Ethernet Adapter
 18...4c cc 6a 24 81 c1 ......Intel(R) Ethernet Connection (2) I219-V
 12...00 ff 68 6e 5d e8 ......Netease UU TAP-Win32 Adapter V9.21
 27...00 ff f9 b1 0f 24 ......TAP-Windows Adapter V9
 21...00 ff ce 62 2e f7 ......SVN Adapter V1.0
  1...........................Software Loopback Interface 1
 38...00 15 5d e3 b5 32 ......Hyper-V Virtual Ethernet Adapter #2
 55...00 15 5d f2 d4 1c ......Hyper-V Virtual Ethernet Adapter #3
 64...00 15 5d 8d 31 75 ......Hyper-V Virtual Ethernet Adapter #4
 60...00 15 5d e3 1e aa ......Hyper-V Virtual Ethernet Adapter #5
===========================================================================

要想通过浏览器直接访问 IPv6 地址,直接使用中括号 [] 包裹 IPv6 地址,例如访问本机的 80 端口,在浏览器地址栏中输入 [::1]:80 即可。和 IPv4 一样,最后的 :80 可以省略。

![通过浏览器访问 IPv6 地址](https://cdn.bun.plus/blog/2020/11/29/通过浏览器访问_IPv6 地址.png)

简单了解 ASP.NET Razor Pages

Razor Pages 是一种比 ASP.NET MVC 更便捷的页面开发方式。

  • Pages 文件夹下创建的 Razor 页面(对应后缀为 .cshtml),文件名与路由通过约定匹配
  • 每个页面对应一个 PageModel,可以理解成 MVC 的 ViewModel,页面可以直接访问到这个对象
  • 可以在 PageModel 方法中响应 GET、POST 等动作

总之 Razor Pages 即可以使用 Razor 语法编写页面,也能用 C# 开发后台代码,但又不需要像 MVC 那样创建 Controller 和 View Model。

创建 Razor Pages 项目

我们直接创建一个 .NET 5 的 Razor Pages 应用:打开 Visual Studio - 创建新项目 - ASP.NET Core Web 应用程序 - 设置好项目名称和路径 - ASP.NET Core Web 应用 ,这样就可以创建 Razor Page 应用了。

创建 Razor Page-应用

可以看到创建好的 Razor Page 应用的结构

Razor Page 项目结构

具体的代码将在下一篇文章中介绍。

设计思路

首先我们需要明白,一个访问者可能有多个 IP 地址,比较常见的情况是拥有一个可以访问公网的 IPv4 地址和一个可以访问公网的 IPv6 地址;也可能只拥有 IPv4 地址,甚至拥有多个 IPv4 地址。

当我们访问一个网站时,操作系统和浏览器会根据一些配置,决定使用 IPv4 地址访问还是 IPv6 地址访问,以及如果有多个 IPv4 地址会使用哪个地址访问,想要说的是:在一个 HTTP 请求中,不会使用多个 IP 地址。所以如果拥有多个 IPv4 地址,我们只有可能获取到其中一个,多个 IPv6 也是同理。

因此,正常情况下,通过我们的网站最多只能获取到访问者的一个 IPv4 地址和一个 IPv6 地址。

获取发起请求时的真实 IP 地址的方案

要想获取到访问者的 IP 并非易事——网络上有很多的代理技术,访问者通过代理服务器访问网站,网站接收到的请求来自代理服务器,这样访问者的真实 IP 就被隐藏了。虽然有诸如 X-Forwarded-For 这样的请求头存在,但它可以伪造,不值得信赖。

获取真实 IP 地址的方案

简单起见,我们直接以服务器接收到的请求的发起方的 IP 地址作为真实 IP 地址,不考虑通过代理访问的情况。

并且因为一般不直接把部署在服务器上的站点直接暴露到公网,因此需要使用 nginx 反向代理我们的站点。nginx 为了能给请求者响应,必然知道请求者的 IP 信息,可以在 nginx 的配置中通过 $remote_addr 获取到。因此我们可以自定义一个名为 X-Real-IP 的头存放 $remote_addr 的值,并在 ASP.NET Razor Page 中获取即可。

当然,我们也需要兼顾本地调试,或者不想配置 nginx 而直接把程序暴露到公网上的情况。

获取其它 IP 地址的方案

如果访问者有多个 IP 地址,上一步只会获得其中一个(只会用一个地址去访问我们的网站),还需要想办法获取访问者的另一个 IP 地址:

  • 如果访问者只拥有 IPv4 地址,那么只能访问 IPv4 地址,无法访问 IPv6 地址
  • 如果访问者只拥有 IPv6 地址,那么只能访问 IPv6 地址,无法访问 IPv4 地址
  • 如果访问者同时拥有 IPv4 和 IPv6 地址,那么 IPv4 地址和 IPv6 地址都可以访问

检测 IPv6 可用性的方案

因此思路很简单:

  • 第一步,我们需要有一个即拥有 IPv4 又拥有 IPv6 地址的网站,这样不论访问者拥有什么地址,都可以访问到我们网站的首页
  • 第二步,我们还需要拥有一个只能通过 IPv4 访问的网站,以及一个只能通过 IPv6 访问的网站,这两个网站需要以 JSON 的形式返回请求者的 IP 地址
    • 当访问者访问到第一步的网站首页后,可以立即知道访问者已经有 IPv4 或者 IPv6 地址,此时需要检查访问者是否还拥有其它的地址
    • 如果访问者访问网站首页时使用的是 IPv4,那么通过 ajax 访问我们只能通过 IPv6 访问的网站
    • 如果访问者访问网站首页时使用的是 IPv6,那么通过 ajax 访问我们只能通过 IPv4 访问的网站
  • 最后,如果 ajax 得到成功的响应,则说明对应地址可以访问,将 ajax 获得的 IP 地址显示到页面上,否则说明访问者没有其它的地址了

也就是说,我们至少需要3个站点,以我的部署情况为例:

  • ip.bun.plus 既能通过 IPv4 访问,又能通过 IPv6 访问,对应第一步的网站首页
  • ipv4.bun.plus 只能通过 IPv4 访问
  • ipv6.bun.plus 只能通过 IPv6 访问

添加多个解析记录

怎么才能使地址既能通过 IPv4 访问,又能通过 IPv6 访问呢。IPv4 对应 DNS 的 A记录,IPv6 对应 AAAA记录,我们只需要分别添加两条主机值一样,但记录类型不同的解析记录即可。添加好之后可以通过 nslookup 看到效果,返回多个 IP 结果即为成功。当然,域名解析这是后话了,现在不着急配置。

下集预告

好了,方案已经讲解结束,下一篇我们将使用 ASP.NET Razor Pages 开发我们的网站了,敬请期待。