SmartMesh Developer Community码农的世界Golang

IPFS Private Network 探秘

2018-12-13  本文已影响54人  cc14514

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-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/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/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并且对 readwrite 进行了有关加密解密的封装

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
}

ReadWrite 的实现中看到了 XORKeyStream ,字面能猜出这是通过数据包和 key 进行异或操作而实现的简单的加密传输,两边的key肯定是一致的,并且每次建立连接时都会交换一个 24 位的 nonce,细心的同学可以去看一下具体的拆分逻辑,分成了 前16 和后 8 ,又做了一些复杂的位移操作用来生成连接通道上使用的key,随机数保证了每次连接通道上的 key 都是不同的。

谁在使用 Protector ?

libp2p 中定义了很多 Option 用来初始化 Host 对象,在前面的几篇入门文章中提及过,其中就有一个叫 libp2p.PrivateNetworkoption,用来为 Host 指定 Protector 。前面的文章也提到过 TcpTransportUpgrader 的调用过程,这个 Protector 就是在 Upgrader.upgrade 中被使用到的,有兴趣的同学可以重新回忆并翻找一下代码。

总结

现在我们知道了私网是通过对通道进行加密来建立的,发出的数据包都用密钥进行一次异或 XOR(packet,securityKey) ,接收端再做一次相同的操作即可还原数据包。这个原理计算机专业的同学应该都了解,如果不了解就演算一下

上一篇下一篇

猜你喜欢

热点阅读