IPFS

分布式文件系统，原理类似于 bt, 通过文件分块，每个块对应 CID 以及各级 Hash 做存储和校验，通过 DHT (Distributed Hash Table) 做查找和路由.

IPFS 文档

https://docs.ipfs.io/ 主要看 Concepts 和 How-tos 部分
IPFS 对应每个 Content 会产生不同的 CID, 对于需要固定链接的需求，可以通过 IPNS 实现，但是 IPNS 不适合于快速变化的内容，IPNS 的更新间隔是以分钟记的

使用 IPFS 做的一些服务

在线音乐: https://github.com/icidasset/diffuse, https://diffuse.sh/

在线视频: https://github.com/download13/ipfstube, http://www.ipfs.guide/, https://ipfstube.erindachtler.me/ https://ipfs.video/

网关测速: https://ipfs.runfission.com/ipns/ipnso.com

Rick Astley - Never Gonna Give You Up: QmcniBv7UQ4gGPQQW2BwbD4ZZHzN3o3tPuNLZCbBchd1zh

网关

官方有一个可用的网关列表: https://ipfs.github.io/public-gateway-checker/ 下面是测试过的一些可用网关

http://ipfs.io

https://ipfs.greyh.at/ (国内可用)

https://ninetailed.ninja/ (国内可用，速度不错)

dweb.link

ipfs.runfission.com (国内可用，速度不错)

http://trusti.id/

http://ipfs.globalupload.io/

go-ipfs

IPFS 的 Go 语言项目是其主要的开源实现: https://github.com/ipfs/go-ipfs/

go-ipfs 的配置说明

详细的配置说明在 https://github.com/ipfs/go-ipfs/blob/master/docs/config.md 这个比手册写的详细

编译 go-ipfs

参考 https://github.com/ipfs/go-ipfs#download-and-compile-ipfs

Github 项目源代码有 30 多 MB, clone 的时间会比较长，如果太慢并且出错，需要加个代理。编译使用 make build 命令，说是编译，其实大部分时间是花在下载依赖库上，
在执行前设置一下 GOPROXY 避免下载超时

Copy
# 检查go版本, 要1.13以上
go version
# 设置代理
git config --global https.proxy 'socks5://127.0.0.1:10080'
# 检查
git config -l
#
git clone https://github.com/ipfs/go-ipfs.git
cd go-ipfs/
# 设置GOPROXY
export GOPROXY=https://goproxy.cn
# 检查
echo $GOPROXY
# 编译
make build
# 检查编译结果
./cmd/ipfs/ipfs version

在 Amlogic S905L 的 Armbian 上运行 IPFS

从 GO IPFS Github 项目下载 Arm64 版本，Armbian 版本为 5.99, 内核 5.30.

Copy
# 下载
wget https://github.com/ipfs/go-ipfs/releases/download/v0.5.1/go-ipfs_v0.5.1_linux-arm64.tar.gz
# 解压
tar xvf go-ipfs_v0.5.1_linux-amd64.tar.gz
# 直接运行 install.sh 安装, 这个脚本会将ipfs目录复制到 /usr/local/bin 下
cd go-ipfs
./install.sh

初始化后，使用普通用户启动

Copy
# 查看版本
ipfs --version
# 初始化节点
ipfs init
# 查看说明
ipfs cat /ipfs/QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc/readme

# 这个命令不会后台运行, 建议先建一个screen session, 在screen session里执行
ipfs daemon

如果 IPFS 不是运行在当前电脑，那么配置需要做两处改动后才能访问 webui

一处是 Addresses.API 和 Addresses.Gateway

Copy
"Addresses": {
...
"API": "/ip4/127.0.0.1/tcp/5001",
"Gateway": "/ip4/127.0.0.1/tcp/8080"
}

将 API 的地址改为 /ip4/0.0.0.0/tcp/5001 这样会监听所有网口，如果你的服务器有公网 IP, 这样会有安全问题，可以将这个 IP 设为内网网口地址.
将 Gateway 的地址改为内网地址或公网地址，如果 IPFS 处于内网，外网可以通过 NAT 访问，这个可以配成内网地址.

另一处是 API.HTTPHeaders, 避免从当前电脑访问 IPFS 的 webui 时，出现 cross site 错误.

Copy
"API": {
"HTTPHeaders": {}
},

在 HTTPHeaders 里增加两项，可以参考 Gateway.HTTPHeaders

Copy
"Access-Control-Allow-Methods": [
"GET","PUT","POST"
],
"Access-Control-Allow-Origin": [
"*"
]

修改完配置，重启 IPFS 后就可以访问 http://IP:5001/webui/ 可以图形化的查看当前节点状态，存储的块数量和大小，连接的节点明细，以及查看当前的配置。这个界面甚至提供了一些演示用的文件浏览.

通过 Gateway, 可以访问 IPFS 上的文件以及远程可以查询到 CID 的文件。任何访问过的文件块都会被缓存，再次访问时，相应的块会直接走缓存，不会再从远程下载.

选择文件上传后，会在页面后台进行传输 (尚未确认是否可以关闭标签页), 在状态页能看到总存储在增长。对于不同大小的文件，其发布可见的延迟是不一样的，对于 10MB 以内的文件，通过远程 gateway 很快就能发现，对于接近 500MB 的文件，大概需要半个小时到一小时。这和节点发布自身的内容 CID 到其他节点的机制有关，因为单个文件块为 256KB (262144Byte), 对于 500MB 大小的文件，会产生 2,000 多个 CID, 发布需要的时间会长得多.

在 webui 上添加文件，需要等文件上传完整后，才能做 pin 操作，如果是通过 CID 上传文件，在提交 CID 后并不会触发文件同步，对这个 CID 进行 pin 操作后，会触发 ipfs 开始从其他节点同步，等文件从其他节点同步完成后，会出现在 pin 列表.

安装到服务

创建文件 /etc/systemd/system/ipfs.service 写入

Copy
[Unit]
Description=IPFS Daemon
After=syslog.target network.target remote-fs.target nss-lookup.target
[Service]
Type=simple
ExecStart=/usr/local/bin/ipfs daemon --enable-namesys-pubsub
User=milton
[Install]
WantedBy=multi-user.target

然后通过 systemctl 添加

配置说明

IPFS 对外提供的端口主要是 3 个: API, Gateway 和 Swarm, 其中

API: 默认使用端口 5001, 在上面提供了 webui, 用于管理控制 IPFS. 设置监听网口时应当注意不要开放到公网

Gateway: 默认使用端口 8080, 在上面提供 ipfs/CID 的内容查找下载服务

Swarm: 默认使用端口 4001, 这个端口用于监听其他 IPFS 节点的请求

Addresses.NoAnnounce 添加需要排除的内网 IP, 这些 IP 不会发布，但是注意不要排除 127.0.0.1 和::1, 这两个貌似被用于其他节点检测当前节点是否支持 ipv4 或 ipv6, 如果排除掉，会无法与其他节点保持连接 (能 ping 能 connect, 但是 swarm peers 里查不到)

Swarm.AddrFilters 添加需要忽略的内网 IP, 对于在 ID 上 announce 内网 IP 的 peer, 这些内网 IP 的地址会被过滤掉

Discovery.MDNS.Enabled 将这个选项改为 false, 就不会在内网发起节点搜索

Peering.Peers 添加需要保持连接的节点

固定节点

对于自建的网络，需要让自己的节点之间保持连接，但是 IPFS 默认的机制下，即使将自建节点设置为 bootstrap, 在启动一段时间后，在连接的节点不断增加后，依然会将与其他自建节点之间的连接关闭。要保持连接，需要使用配置文件中的 Peering 部分，格式如下，第二个为 ipfs.runfission.com 的接入地址

Copy
{
"Peering": {
"Peers": [
{
"ID": "QmPeerID1",
"Addrs": ["/ip4/18.1.1.1/tcp/4001"]
},
{
"ID": "QmVLEz2SxoNiFnuyLpbXsH6SvjPTrHNMU88vCQZyhgBzgw",
"Addrs": ["/ip4/3.215.160.238/tcp/4001", "/ip4/3.215.160.238/udp/4001/quic"]
}
]
}
...
}

使用 Peering 配置的节点:

在连接管理中会保护与这个节点之间的连接，IPFS 永远不会主动 (自动) 关闭这个连接，在连接数达到上限时也不会关闭这个连接.

在启动时就会建立连接

如果因为网络原因或对方节点掉线等原因导致连接丢失，IPFS 会不断尝试重新连接，尝试的间隔长度在 5 秒至 10 分钟之间随机.

在公网 NAT 下运行 IPFS

运行环境

公网 IP 的服务器是 Centos7, 公网 IP 为 118.119.120.121, 内网 IP 为 192.168.13.10
内网服务器 Ubuntu18.04, 内网 IP 为 192.168.13.25

在公网服务器上设置端口转发

配置一下全局转发开关

Copy
firewall-cmd --permanent --zone=public --add-masquerade

将公网 IP 端口 4002 转发至内网服务器的 4001 端口

Copy
# TCP端口转发
firewall-cmd --permanent --zone=public --add-forward-port=port=4002:proto=tcp:toaddr=192.168.13.25:toport=4001
# UDP端口转发
firewall-cmd --permanent --zone=public --add-forward-port=port=4002:proto=udp:toaddr=192.168.13.25:toport=4001
# 设置生效
firewall-cmd --reload
# 检查
firewall-cmd --zone=public --list-all

如果网关是 OpenWRT 路由，则直接在防火墙 -> 端口转发中添加转发规则即可。注意在添加完转发规则后，还需要在通信规则中允许 WAN 到此设备此端口的访问.

限制连接的节点数量

需要通过 Swarm/ConnMgr/HighWater 这个参数，在 0.6.0 时这个设置并不能很好地工作，长时间运行服务器后节点数量会远超设置的限制，在 0.7.0 上是有效的

Copy
"Swarm": {
...
"ConnMgr": {
"GracePeriod": "30s",
"HighWater": 500,
"LowWater": 100,
"Type": "basic"
},
...
}

在内网服务器上配置 IPFS 服务

Copy
# 安装过程略

# 初始化为服务器模式
ipfs init --profile=server

# 修改配置, 看下面具体说明
vi .ipfs/config

# 启动
ipfs daemon

服务器模式和普通模式相比，有几点变化

Addresses.NoAnnounce 服务器模式会列出所有的内网 IP, 这些 IP 不会发布

Swarm.AddrFilters 服务器模式会列出所有内网 IP, 对于使用内网 IP 来连接的 peer, 都会被过滤掉

Discovery.MDNS.Enabled 服务器模式时这个选项为 false, 避免在内网发起节点搜索

需要配置的地方除了普通节点的 API, Gateway 和 API HTTPHeaders, 还需要配置 Addresses.Announce , 需要添加本节点的公网 IP 和端口

Copy
"Announce": [
"/ip4/118.119.120.121/tcp/4002",
"/ip4/118.119.120.121/udp/4002/quic"
],

遇到的问题

使用 OpenWRT 配置的网关不存在这个问题，但是使用 Centos 做网关时似乎不能将外网 IP 正确传入，导致大量第三方节点的显示 IP 为网关 IP, 对本节点连接可以成功，但是 ping 失败。所以对于 Swarm.AddrFilters 里面的列表要删除同网段的部分.

具体原因可以通过本节点的 swarm peers 看到，从本节点主动连接的节点，记录的是公网 IP, 但是本节点被动连接 (即通过网关 118.119.120.121 连接) 的节点，记录的都是网关的内网 IP, 按照 AddrFilters 的规则，这些节点会被丢弃。就会出现 ipfs swarm connect 成功，但是 ipfs ping 失败的问题.

Copy
$ ipfs swarm peers
/ip4/104.131.131.82/udp/4001/quic/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
/ip4/111.231.85.77/tcp/4001/p2p/QmWv1eLMNHPpwYKzREQBEpDfYjW6YXCrVpVyBZVjAuSd2i
...
/ip4/192.168.13.10/tcp/10041/p2p/QmXUZth5Pr2u1cW65F7gUeFwjkZFfduE1dwqiysNnrTwXd
/ip4/192.168.13.10/tcp/10053/p2p/QmPM3bepMUKpYTza67coD1Ar3efL7FPBFbGRMc42QLf4q9
/ip4/192.168.13.10/tcp/10202/p2p/QmVBzjW2MyNrSuR48GjcqB6SAJTnw8Y9zaFbgbegh4bRx4
/ip4/192.168.13.10/tcp/1024/p2p/QmbBEfFfw59Vya9CJuzgt4qj9f57sPZGiyK7fup8iKqkTr
/ip4/192.168.13.10/tcp/1025/p2p/QmcmhwCeLkBJvcq6KJzN58BRZg1B1N8m3uA3JyQKpVn64E
...
/ip4/192.168.13.10/udp/6681/quic/p2p/QmcSCBpek4YF5aAsY7bUMxiL7tacoYMeXUJUpU4wctqX4w
/ip4/192.168.13.10/udp/8050/quic/p2p/QmWeuXCNKAHfbineKMqo3U3dvVSz2em1w67pj5Up6tkUXo
/ip4/206.189.69.143/tcp/31071/p2p/12D3KooWHDr5W3Tse17mr4HSzuQm44dVQYp8Bb638mQknsyeHXSP
/ip4/206.189.69.250/tcp/30511/p2p/12D3KooWRd1BNPd8PMfxpCT7TNCFY4XSZsy8v8Cmm36H136yxzub
...

再进一步检查是否可以从本节点访问被动连接的节点，取其中一个 Peer ID 做 ping 测试

Copy
ipfs ping QmXUZth5Pr2u1cW65F7gUeFwjkZFfduE1dwqiysNnrTwXd
PING QmXUZth5Pr2u1cW65F7gUeFwjkZFfduE1dwqiysNnrTwXd.
Pong received: time=26.43 ms
Pong received: time=25.70 ms
Pong received: time=26.31 ms
...

说明这些记录为网关内网 IP 的节点是可用的，应当保留

这些通过网关连接的节点，可以分为两类：无公网 IP 的节点和有公网 IP 的节点:

对于有公网 IP 的节点，尚不清楚是否会在初次连接后使用对方 Announce 的地址回 ping, 如果会并且会根据此结果更新节点的地址，那么这些节点会在这个显示为内网 IP 的列表中短暂停留后，更新为公网 IP 地址，这样在其他节点接入后，共享节点信息时，其他节点可以通过其公网 IP 地址进行连接.

对于无公网 IP 的节点，服务器节点根据对方 Announce 的地址回 ping 是无法连接的，只能通过本方网关的内网 IP 进行连接，所以会一直停留在这个显示为内网 IP 的列表中。这些节点是无法共享给其他节点的.

优化下载速度

如果是希望通过 CID 读取文件，首先要选择速度较快的网关，通过 ipfs.io 网关获取文件是最可靠的，但是因为连接问题，速度可能会很慢.

如果是希望通过 CID 发送文件，要务必保证对方使用的网关在当前存放文件的实例的连接列表中，所以维护一个速度较快的网关列表很重要，将这些网关加入自己的 Peering.Peers, 将极大提升自己的文件发布速度.

升级 IPFS

大版本的升级，需要使用 fs-repo-migrations 工具，参考 Doc 里的说明，就是两步，备份.ipfs 目录，运行 fs-repo-migrations 命令，在这之前需要把 ipfs 服务停掉.

IPFS Desktop

在 Windows10 下安装 IPFS Desktop, 默认安装到用户目录下 C:\Users\Milton\AppData\Local\Programs\IPFS Desktop, 安装完成后程序目录 265M,
数据目录在 C:\Users\Milton.ipfs, 格式和内容与 go-ipfs 无异。后台有两个 IPFS Desktop 进程，两个 ipfs 进程，共占用内存约 500M.

状态窗口实际上是一个 webview, 展示了 webui 的内容.

配置只是在默认的配置上将水位线改为 50, 300, 其他是一样的.

资源如果存在 (缓存) 于直接连接的节点则可以访问，否则无法访问.

IPFS 私有网络和集群

参考 https://labs.eleks.com/2019/03/ipfs-network-data-replication.html

将 IPFS 运行为服务，开机自启动

Copy
# 创建服务文件
sudo vi /etc/systemd/system/ipfs.service

# 文件内容
[Unit]
Description=IPFS Daemon
After=syslog.target network.target remote-fs.target nss-lookup.target
[Service]
Type=simple
ExecStart=/usr/local/bin/ipfs daemon --enable-namesys-pubsub
User=root
[Install]
WantedBy=multi-user.target
# 文件内容结束

# 添加服务
sudo systemctl daemon-reload
sudo systemctl enable ipfs
sudo systemctl start ipfs
sudo systemctl status ipfs

将 IPFS Cluster 添加为服务

Copy
# 创建服务
sudo nano /etc/systemd/system/ipfs-cluster.service

# 文件内容开始, 注意After里有ipfs服务, 以此保证启动顺序
[Unit]
Description=IPFS-Cluster Daemon
Requires=ipfs
After=syslog.target network.target remote-fs.target nss-lookup.target ipfs
[Service]
Type=simple
ExecStart=/home/ubuntu/gopath/bin/ipfs-cluster-service daemon
User=root
[Install]
WantedBy=multi-user.target
# 文件内容结束

# 添加到系统服务
sudo systemctl daemon-reload
sudo systemctl enable ipfs-cluster
sudo systemctl start ipfs-cluster
sudo systemctl status ipfs-cluster

IPFS 的应用场景

经过实际测试，对于在公众网络中的 IPFS 节点，一旦建立了连接进行 CID 的发布和读取速度是很快的，除去网络本身的延迟，从请求到开始传输基本上在 2 秒内，传输速度取决于两点间的带宽.

文件共享

IPFS 对内容的索引形式很适合做团队间的文件共享，因为每一次改动都会产生索引变化，可以做版本化控制。分布式的文件接入点和下载点方便集群伸缩扩展，带缓存的特性能降低热点数据对带宽资源的影响.

音视频分发

用于取代现有的 PT 下载网络。因为 PT 传播的单个文件很大，需要对整个 PT 群中的节点做统一的 pin 管理，一方面确保各终端节点对热点内容的访问速度，另一方面确保长尾内容有足够的备份，不会丢失.

流媒体和下载加速

IPFS 的特性，天然地可以用于取代 CDN 服务。静态文件例如图片，CSS, JS, 以及压缩包，甚至一些时效性要求不高的直播业务。现在运营商宽带已经普及 IPv6, 这些有公网 IPv6 地址的家庭带宽资源可以充分利用起来做区域内容加速.

libp2p

libp2p 是从 IPFS 中独立出来的一个封装好的 p2p 模块，模块中已经内置了 PeerId, MultiAddress, Protocol Handler 这些机制，可以很方便扩展出自己的应用.

Go 语言的实现 https://github.com/libp2p/go-libp2p, 样例代码 https://github.com/libp2p/go-libp2p-examples

在代码里的使用，一般是以下步骤:

创建 Host

对指定的协议，在 Host 上 SetStreamHandler

如果本地还有端口提供服务，则创建对应服务并监听端口

指定目标节点和协议，创建 Stream

往 Stream 写数据

从 Stream 读数据，根据业务需求关闭或不关闭 Stream

在 Host 启动后如果需要保持运行，可以使用以下方式

Copy
# 方法1, 使用select
select {} // hang forever

# 方法2, 使用内置服务的ListenAndServe
http.ListenAndServe(serveArgs, p)

# 方法3, 使用channel
<-make(chan struct{}) // hang forever