aboutsummaryrefslogtreecommitdiffstats
path: root/p2p/rlpx.go
blob: 9fd1aed1f6abc56e371cda696756f8155c9e377e (plain) (blame)
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
package p2p

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "crypto/hmac"
    "errors"
    "hash"
    "io"

    "github.com/ethereum/go-ethereum/rlp"
)

var (
    zeroHeader = []byte{0xC2, 0x80, 0x80}
    zero16     = make([]byte, 16)
)

type rlpxFrameRW struct {
    conn io.ReadWriter

    macCipher  cipher.Block
    egressMAC  hash.Hash
    ingressMAC hash.Hash
}

func newRlpxFrameRW(conn io.ReadWriter, macSecret []byte, egressMAC, ingressMAC hash.Hash) *rlpxFrameRW {
    cipher, err := aes.NewCipher(macSecret)
    if err != nil {
        panic("invalid macSecret: " + err.Error())
    }
    return &rlpxFrameRW{conn: conn, macCipher: cipher, egressMAC: egressMAC, ingressMAC: ingressMAC}
}

func (rw *rlpxFrameRW) WriteMsg(msg Msg) error {
    ptype, _ := rlp.EncodeToBytes(msg.Code)

    // write header
    headbuf := make([]byte, 32)
    fsize := uint32(len(ptype)) + msg.Size
    putInt24(fsize, headbuf) // TODO: check overflow
    copy(headbuf[3:], zeroHeader)
    copy(headbuf[16:], updateHeaderMAC(rw.egressMAC, rw.macCipher, headbuf[:16]))
    if _, err := rw.conn.Write(headbuf); err != nil {
        return err
    }

    // write frame, updating the egress MAC while writing to conn.
    tee := io.MultiWriter(rw.conn, rw.egressMAC)
    if _, err := tee.Write(ptype); err != nil {
        return err
    }
    if _, err := io.Copy(tee, msg.Payload); err != nil {
        return err
    }
    if padding := fsize % 16; padding > 0 {
        if _, err := tee.Write(zero16[:16-padding]); err != nil {
            return err
        }
    }

    // write packet-mac. egress MAC is up to date because
    // frame content was written to it as well.
    _, err := rw.conn.Write(rw.egressMAC.Sum(nil))
    return err
}

func (rw *rlpxFrameRW) ReadMsg() (msg Msg, err error) {
    // read the header
    headbuf := make([]byte, 32)
    if _, err := io.ReadFull(rw.conn, headbuf); err != nil {
        return msg, err
    }
    fsize := readInt24(headbuf)
    // ignore protocol type for now
    shouldMAC := updateHeaderMAC(rw.ingressMAC, rw.macCipher, headbuf[:16])
    if !hmac.Equal(shouldMAC[:16], headbuf[16:]) {
        return msg, errors.New("bad header MAC")
    }

    // read the frame content
    framebuf := make([]byte, fsize)
    if _, err := io.ReadFull(rw.conn, framebuf); err != nil {
        return msg, err
    }
    rw.ingressMAC.Write(framebuf)
    if padding := fsize % 16; padding > 0 {
        if _, err := io.CopyN(rw.ingressMAC, rw.conn, int64(16-padding)); err != nil {
            return msg, err
        }
    }
    // read and validate frame MAC. we can re-use headbuf for that.
    if _, err := io.ReadFull(rw.conn, headbuf); err != nil {
        return msg, err
    }
    if !hmac.Equal(rw.ingressMAC.Sum(nil), headbuf) {
        return msg, errors.New("bad frame MAC")
    }

    // decode message code
    content := bytes.NewReader(framebuf)
    if err := rlp.Decode(content, &msg.Code); err != nil {
        return msg, err
    }
    msg.Size = uint32(content.Len())
    msg.Payload = content
    return msg, nil
}

func updateHeaderMAC(mac hash.Hash, block cipher.Block, header []byte) []byte {
    aesbuf := make([]byte, aes.BlockSize)
    block.Encrypt(aesbuf, mac.Sum(nil))
    for i := range aesbuf {
        aesbuf[i] ^= header[i]
    }
    mac.Write(aesbuf)
    return mac.Sum(nil)
}

func readInt24(b []byte) uint32 {
    return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16
}

func putInt24(v uint32, b []byte) {
    b[0] = byte(v >> 16)
    b[1] = byte(v >> 8)
    b[2] = byte(v)
}