基于IPv6的服务访问

本文旨在介绍如何在不使用第三方服务(如内网穿透)的情况下,将本地服务提供在公网访问。

适用性

1、不希望流量经过第三方服务的情况(如本地储存的远程访问等需要私密性的服务)

2、需要服务端与客户端都拥有“可聚集全球单播ipv6地址”,也就是所谓的“公网ipv6”

3、对带宽有要求,但是预算不足,付不起云服务器带宽,家里上传带宽也还算可观的

4、本教程的动态域名解析服务是自己手搓的,如果能找到现成的插件那当然更方便啦!

5、端口转发实际上安全性可能更高,适用性也应该更广,详情可以见其他教程,此处仅涉及通过服务端的动态域名解析实现的直接访问。

原理

如果你配置过局域网服务,你一定在一定程度上理解服务端与客户端在网络中的关系。事实上,如果ipv4没有枯竭,或者说nat没有大规模应用,实际上任何地方对服务的访问都应该像局域网中方便(但与此同时也会增加一定受到攻击的风险)。在刨除了各级路由对于网络包的丢弃、拒绝等处理后,实际上,在ipv6的世界里,全球互联网就是一个局域网。

基于这样的理念,ipv6实际上可以方便我们将服务开放。而通过动态域名解析,我们可以避免ipv6地址动态分配下丢失地址的问题,且解决ipv6地址过长不好应用的问题。这样,亲朋好友便可以方便放心的访问我们的服务啦!一起玩mc,看h片什么的(不h片还是不能分享的,要遵守互联网法律法规!)

原料

1、一个域名,以及一项域名解析服务。(本人不才,是从aliyun购买的域名与域名解析服务,这俩还是要钱的,但胜在方便,而且对于多项服务的布置服务与域名只需要购买一次。)

2、一条支持分配公网ipv6的宽带

3、一个支持设置通信规则的路由器(我用的软路由,直接在openwrt防火墙里设置的转发,别的路由器可以查查)

4、一个正常提供服务的主机

开始教程

确定家里确实有ipv6地址,或者能开启ipv6服务。

首先掏出你的联网手机,打开wifi详情页查看有没有非fc00/fe80开头的ipv6地址。

或者电脑按 Win+R 输入cmd 按回车 然后输入ipconfig回车,你找到以太网适配器 以太网(或者你正在使用的网口一栏),查看有没有非fc00/fe80开头的地址。

如果都没有,自行百度自家路由器如何开启ipv6功能,或者很遗憾,确实没有ipv6地址分配。

打开对应端口的入站流量转发

如果你像我一样使用OpenWrt,你只需要在 网络-防火墙-通信规则 下,如下新增条目并置顶(譬如开一个MC服):

既然是ipv6,那就选择仅ipv6,避免被攻击。其他服务则开启对应端口就行了。

在服务端部署DDNS服务

和端口转发不同,这里我的ddns服务部署在服务端上。由于网上没找到阿里云的openwrt ddns插件,我查api用python写了一个ddns脚本,通过计划任务周期运行。

首先你需要有阿里云的域名解析服务,并生成一份ACCESSKEY:

参考 创建AccessKey (aliyun.com)

然后在服务端部署PYTHON环境,并且安装以下两个依赖:

1
2
pip install alibabacloud_tea_util
pip install alibabacloud_alidns20150109

使用此脚本文件(右键下载,源码见下):

DDNS.py
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
from alibabacloud_alidns20150109.client import Client as Alidns20150109Client
from alibabacloud_tea_openapi import models as open_api_models
from alibabacloud_alidns20150109 import models as alidns_20150109_models
from alibabacloud_tea_util import models as util_models
from alibabacloud_tea_util.client import Client as UtilClient
import socket

def getipv6_bysocket():
host_ipv6=[]
ips=socket.getaddrinfo(socket.gethostname(),80)
for ip in ips:
if ip[4][0].startswith('24'):
#2408 中国联通
#2409 中国移动
#240e 中国电信
# print(ip[4][0])
host_ipv6.append(ip[4][0])
return host_ipv6

class DDNS:
def __init__(self):
pass

# 创建访问用户
@staticmethod
def create_client(
access_key_id: str,
access_key_secret: str,
) -> Alidns20150109Client:
config = open_api_models.Config(
access_key_id=access_key_id,
access_key_secret=access_key_secret
)
# 访问的域名
config.endpoint = f'alidns.cn-hangzhou.aliyuncs.com'
return Alidns20150109Client(config)

def get_Record(
client,
domain_name,
rr = '@',
type = 'AAAA'
):
describe_domain_records_request = alidns_20150109_models.DescribeDomainRecordsRequest(
domain_name=domain_name
)
runtime = util_models.RuntimeOptions()
try:
# 复制代码运行请自行打印 API 的返回值
request = client.describe_domain_records_with_options(describe_domain_records_request, runtime)
print("Lookup Success.")
except Exception as error:
# 如有需要,请打印 error
print("Lookup Failed.")
print(UtilClient.assert_as_string(error.message))


try:
record = [i for i in request.body.domain_records.record if i.rr == rr and i.type == type]
# 返回一个ip地址
return record[0]
except Exception as error:
return 0

def initDNS(
client,
domain_name,
rr,
type,
value
):
add_domain_record_request = alidns_20150109_models.AddDomainRecordRequest(
domain_name=domain_name,
rr=rr,
type=type,
value=value
)
runtime = util_models.RuntimeOptions()
try:
# 复制代码运行请自行打印 API 的返回值
client.add_domain_record_with_options(add_domain_record_request, runtime)
print("Init Success.")
except Exception as error:
# 如有需要,请打印 error
print("Init Error.")
print(UtilClient.assert_as_string(error.message))


def syncDNS(
client,
rr,
type,
value,
record_id
):
update_domain_record_request = alidns_20150109_models.UpdateDomainRecordRequest(
rr=rr,
type=type,
value=value,
record_id=record_id
)
runtime = util_models.RuntimeOptions()
try:
# 复制代码运行请自行打印 API 的返回值
client.update_domain_record_with_options(update_domain_record_request, runtime)
print("Sync Success.")
except Exception as error:
# 如有需要,请打印 error
print("Sync Eerror. ID:", record_id)
print(UtilClient.assert_as_string(error.message))


@staticmethod
def ddnsServer(
# 在下面填上你的 AccessKey ID,
id: str = 'your accesskey id',
# 在下面填上你的 AccessKey Secret,
secret: str = 'your accesskey secret',
# 在下面填上你的域名
domain_name: str = 'your.domain',
# 解析类型: 我们用ipv6所以是'AAAA', 如果是ipv4就是'A'
type: str = 'AAAA',
# 在下面填上你的主机名
rr: str = 'name'
):#
client = DDNS.create_client(id, secret)
get = DDNS.get_Record(client=client, domain_name=domain_name, rr=rr, type=type)
local = getipv6_bysocket()[0]
value = get.value
id = str(get)
id = id[id.find("'RecordId':")+13:id.find("', 'Status'")]
if get == 0:
# 未注册
DDNS.initDNS(client, domain_name, rr, type, local)

elif value == local:
# 无需更新
print("已经是最新。")
else:
DDNS.syncDNS(client=client, rr=rr, type=type, value = local, record_id = id)

if __name__ == '__main__':
DDNS.ddnsServer()

最后让DDNS脚本周期性启动。

如何让ddns脚本在服务端计划启动可以参考windows 10 如何设定计划任务自动执行 python 脚本? - 知乎

嫌麻烦可以等我学会打包python脚本(只不过不知道等多久略略)

4、运行一次

你就能在阿里云解析看到解析记录啦!就能成功在公网通过ipv6访问自家的服务咯~

有其他问题欢迎留言w~~