How to enable
首先使用 ipfs-swarm-key-gen 生成 swarm.key
go get github.com/Kubuxu/go-ipfs-swarm-key-gen/ipfs-swarm-key-gen
ipfs-swarm-key-gen > ~/.ipfs/swarm.key
要加入一個私有網絡首先要得到這個網絡的 swarm.key
並保存到 ~/.ipfs/swarm.key
中,如果自定了 IPFS_PATH
,那麼就將它放入自定義的路徑下。
當啓用私網配置後,將無法再使用默認的 bootnode
了,需要設置爲私網的 bootnode
。
爲了防止私網節點嘗試訪問默認的 bootnode
我們先將其清除:
ipfs bootstrap rm --all
然後像這樣來添加私網自己的 bootnode
ipfs bootstrap add <multiaddr>
例如:
ipfs bootstrap add /ip4/104.236.76.40/tcp/4001/ipfs/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64
網絡中的 bootnode
節點與其他節點沒有任何不同,所以製作一個 bootnode
還是非常容易的。
也可以通過設置環境變量 LIBP2P_FORCE_PNET=1
來強行啓用私網配置,如果沒有正確配置 swarm.key
會導致 daemon 啓動失敗
- 參考資料 IPFS官方文檔
原理分析
ipfs-swarm-key-gen
首先看看 key 是如何生成的
//github.com/Kubuxu/go-ipfs-swarm-key-gen/blob/master/ipfs-swarm-key-gen/main.go
func main() {
key := make([]byte, 32)
_, err := rand.Read(key)
if err != nil {
log.Fatalln("While trying to read random source:", err)
}
fmt.Println("/key/swarm/psk/1.0.0/")
fmt.Println("/base16/")
fmt.Print(hex.EncodeToString(key))
}
是不是太簡單了?生成一個 32 位的隨機數,用 hex.Encode 成一個 64 位 16進制數,談下一話題。
How to use swarm.key
首先我們來看一下 libp2p 中的 pnet 接口和實現
- go-libp2p-interface-pnet
// go-libp2p-interface-pnet/interface.go
// Protector interface is a way for private network implementation to be transparent in
// libp2p. It is created by implementation and use by libp2p-conn to secure connections
// so they can be only established with selected number of peers.
type Protector interface {
// Wraps passed connection to protect it
Protect(net.Conn) (net.Conn, error)
// Returns key fingerprint that is safe to expose
Fingerprint() []byte
}
Protector 接口說它是實現私網的一種方式,由 libp2p-conn
調用創建加密連接
- go-libp2p-pnet
// go-libp2p-pnet/protector.go
type protector struct {
psk *[32]byte
fingerprint []byte
}
func (p protector) Protect(in net.Conn) (net.Conn, error) {
return newPSKConn(p.psk, in)
}
func (p protector) Fingerprint() []byte {
return p.fingerprint
}
這個 protector
類實現了 Protector
接口,在創建這個類時會去反序列化 key
得到 psk
,那個就不重要了,比較重要的是 newPSKConn
這個包裝
func newPSKConn(psk *[32]byte, insecure net.Conn) (net.Conn, error) {
if insecure == nil {
return nil, errInsecureNil
}
if psk == nil {
return nil, errPSKNil
}
return &pskConn{
Conn: insecure,
psk: psk,
}, nil
}
上面的 newPSKConn
返回了 pskConn
,這個類繼承了 net.Conn
並且對 read
和 write
進行了有關加密解密的封裝
type pskConn struct {
net.Conn
psk *[32]byte
writeS20 cipher.Stream
readS20 cipher.Stream
}
func (c *pskConn) Read(out []byte) (int, error) {
if c.readS20 == nil {
// 如果是第一個包,那麼前 24 個字節一定是一個隨機數
// 因爲下面的 Write 方法第一個包就是放了一個 24 byte 的 nonce 在前面
nonce := make([]byte, 24)
_, err := io.ReadFull(c.Conn, nonce)
if err != nil {
return 0, errShortNonce
}
// salsa20.New 這個實現過程比較複雜,目的是返回一個標準庫中 cipher.Stream 接口的實現類
c.readS20 = salsa20.New(c.psk, nonce)
}
maxn := uint32(len(out))
in := mpool.ByteSlicePool.Get(maxn).([]byte) // get buffer
defer mpool.ByteSlicePool.Put(maxn, in) // put the buffer back
in = in[:maxn] // truncate to required length
n, err := c.Conn.Read(in) // read to in
if n > 0 {
// 代碼看到這裏就可以省略後面的處理了,因爲答案已經出來了
// 直接去看標準庫中對於 cipher.Stream.XORKeyStream 的定義即可
c.readS20.XORKeyStream(out[:n], in[:n]) // decrypt to out buffer
}
return n, err
}
func (c *pskConn) Write(in []byte) (int, error) {
......
c.writeS20.XORKeyStream(out, in) // encrypt
return c.Conn.Write(out) // send
}
從 Read
和 Write
的實現中看到了 XORKeyStream
,字面能猜出這是通過數據包和 key
進行異或操作而實現的簡單的加密傳輸,兩邊的key肯定是一致的,並且每次建立連接時都會交換一個 24
位的 nonce
,細心的同學可以去看一下具體的拆分邏輯,分成了 前16 和後 8 ,又做了一些複雜的位移操作用來生成連接通道上使用的key,隨機數保證了每次連接通道上的 key 都是不同的。
誰在使用 Protector ?
- 答案:go-libp2p-transport-upgrader
在 libp2p
中定義了很多 Option
用來初始化 Host
對象,在前面的幾篇入門文章中提及過,其中就有一個叫 libp2p.PrivateNetwork
的 option
,用來爲 Host
指定 Protector
。前面的文章也提到過 TcpTransport
到 Upgrader
的調用過程,這個 Protector
就是在 Upgrader.upgrade
中被使用到的,有興趣的同學可以重新回憶並翻找一下代碼。
總結
現在我們知道了私網是通過對通道進行加密來建立的,發出的數據包都用密鑰進行一次異或 XOR(packet,securityKey)
,接收端再做一次相同的操作即可還原數據包。這個原理計算機專業的同學應該都瞭解,如果不瞭解就演算一下
- 演算過程: 設 packet = 1 , securityKey = 1
securityPacket = xor(packet, securityKey) = xor(1,1) = 0
packet = xor(securityPacket,securityKey) = xor(0,1) = 1