aboutsummaryrefslogtreecommitdiff
path: root/src/gnunet/service/dht/path/handling.go
blob: 7ad91df86bb26eb5a3d699e64de8cff53abbb2fe (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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
// This file is part of gnunet-go, a GNUnet-implementation in Golang.
// Copyright (C) 2019-2022 Bernd Fix  >Y<
//
// gnunet-go is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// gnunet-go is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: AGPL3.0-or-later

package path

import (
	"gnunet/crypto"
	"gnunet/enums"
	"gnunet/util"
	"strings"

	"github.com/bfix/gospel/data"
	"github.com/bfix/gospel/logger"
)

//----------------------------------------------------------------------
// Path handling
//----------------------------------------------------------------------

// Path is the complete list of verified hops a message travelled.
// It also keeps the associated block hash and expiration time of
// the request for signature verification purposes.
type Path struct {
	Flags       uint16              `order:"big"`    // flags
	BlkHash     *crypto.HashCode    ``               // block hash value
	Expire      util.AbsoluteTime   ``               // expiration time
	TruncOrigin *util.PeerID        `opt:"(IsUsed)"` // truncated origin (optional)
	NumList     uint16              `order:"big"`    // number of list entries
	SplitPos    uint16              `order:"big"`    // optional split position
	List        []*Entry            `size:"NumList"` // list of path entries
	LastSig     *util.PeerSignature `opt:"(Isused)"` // last hop signature
	LastHop     *util.PeerID        `opt:"(IsUsed)"` // last hop peer id
}

// IsUsed checks if an optional field is used
func (p *Path) IsUsed(field string) bool {
	switch field {
	case "TruncOrigin":
		return p.Flags&enums.DHT_RO_TRUNCATED != 0
	case "LastSig", "LastHop":
		return p.Flags&enums.DHT_RO_RECORD_ROUTE != 0
	}
	return false
}

// NewPath returns a new, empty path
func NewPath(bh *crypto.HashCode, expire util.AbsoluteTime) *Path {
	return &Path{
		Flags:       0,
		BlkHash:     bh,
		Expire:      expire,
		TruncOrigin: nil,
		NumList:     0,
		SplitPos:    0,
		List:        make([]*Entry, 0),
		LastSig:     nil,
		LastHop:     nil,
	}
}

// NewPathFromBytes reconstructs a path instance from binary data. The layout
// of the data must match with the layout used in Path.Bytes().
func NewPathFromBytes(buf []byte) (path *Path, err error) {
	if len(buf) == 0 {
		return
	}
	path = new(Path)
	err = data.Unmarshal(&path, buf)
	return
}

// Size of the binary representation (in message)
func (p *Path) Size() uint {
	var size uint
	if p.TruncOrigin != nil {
		size += p.TruncOrigin.Size()
	}
	size += uint(p.NumList) * p.List[0].Size()
	if p.LastSig != nil {
		size += p.LastSig.Size() + p.LastHop.Size()
	}
	return size
}

// Bytes returns a binary representation
func (p *Path) Bytes() []byte {
	buf, _ := data.Marshal(p)
	return buf
}

// Clone path instance
func (p *Path) Clone() *Path {
	return &Path{
		Flags:       p.Flags,
		BlkHash:     p.BlkHash,
		Expire:      p.Expire,
		TruncOrigin: p.TruncOrigin,
		NumList:     p.NumList,
		SplitPos:    p.SplitPos,
		List:        util.Clone(p.List),
		LastSig:     p.LastSig,
		LastHop:     p.LastHop,
	}
}

// NewElement creates a new path element from data
func (p *Path) NewElement(pred, signer, succ *util.PeerID) *Element {
	return &Element{
		_ElementData: _ElementData{
			Expire:          p.Expire,
			BlockHash:       p.BlkHash,
			PeerPredecessor: pred,
			PeerSuccessor:   succ,
		},
		Entry: Entry{
			Signer:    signer,
			Signature: nil,
		},
	}
}

// Add new path element with signature (append to path)
func (p *Path) Add(elem *Element) {
	// append path element if we have a last hop signature
	if p.LastSig != nil {
		e := &Entry{
			Signer:    elem.PeerPredecessor,
			Signature: p.LastSig,
		}
		p.List = append(p.List, e)
		p.NumList++
	}
	// update last hop signature
	p.LastSig = elem.Signature
	p.LastHop = elem.Signer
	p.Flags |= enums.DHT_RO_RECORD_ROUTE
}

// Verify path: process list entries from right to left (decreasing index).
// If an invalid signature is encountered, the path is truncated; only checked
// elements up to this point are included in the path (left trim).
// The method does not return a state; if the verification fails, the path is
// corrected (truncated or deleted) and would always verify OK.
func (p *Path) Verify(local *util.PeerID) {

	// do we have path elements?
	if len(p.List) == 0 {
		// no elements: last hop signature available?
		if p.LastSig == nil {
			// no: nothing to verify
			return
		}
		// get predecessor (either 0 or truncated origins)
		pred := util.NewPeerID(nil)
		if p.TruncOrigin != nil {
			pred = p.TruncOrigin
		}
		// check last hop signature
		pe := p.NewElement(pred, p.LastHop, local)
		ok, err := pe.Verify(p.LastSig)
		if err != nil || !ok {
			logger.Println(logger.WARN, "[path] Dropping path (invalid last signature)")
			// remove last hop signature and truncated origin; reset flags
			p.LastSig = nil
			p.LastHop = nil
			p.TruncOrigin = nil
			p.Flags = 0
		}
		return
	}
	// yes: process list of path elements
	signer := p.LastHop
	sig := p.LastSig
	succ := local
	num := len(p.List)
	var pred *util.PeerID
	for i := num - 1; i >= 0; i-- {
		if i == -1 {
			if p.TruncOrigin != nil {
				pred = p.TruncOrigin
			} else {
				pred = util.NewPeerID(nil)
			}
		} else {
			pred = p.List[i].Signer
		}
		pe := p.NewElement(pred, signer, succ)
		ok, err := pe.Verify(sig)
		if err != nil || !ok {
			// we need to truncate:
			logger.Printf(logger.WARN, "[path] Truncating path (invalid signature at hop %d)", i)

			// are we at the end of the list?
			if i == num-1 {
				// yes: the last hop signature failed -> reset path
				p.LastSig = nil
				p.LastHop = nil
				p.TruncOrigin = nil
				p.Flags = 0
				p.List = make([]*Entry, 0)
				return
			}
			// trim list
			p.Flags |= enums.DHT_RO_TRUNCATED
			p.TruncOrigin = signer
			size := num - 2 - i
			list := make([]*Entry, size)
			if size > 0 {
				copy(list, p.List[i+2:])
			}
			p.List = list
			return
		}
		// check next path element
		succ = signer
		signer = pred
		if i != -1 {
			sig = p.List[i].Signature
		}
	}
}

// String returns a human-readable representation
func (p *Path) String() string {
	var hops []string
	if p != nil {
		if p.TruncOrigin != nil {
			hops = append(hops, p.TruncOrigin.Short())
		}
		for _, e := range p.List {
			hops = append(hops, e.Signer.Short())
		}
		if p.LastHop != nil {
			hops = append(hops, p.LastHop.Short())
		}
	}
	// trim to sensible length for display
	if num := len(hops); num > 8 {
		trim := make([]string, 9)
		copy(trim[:4], hops[:4])
		trim[4] = "..."
		copy(trim[5:], hops[num-5:])
	}
	return "[" + strings.Join(hops, "-") + "]"
}