mirror of
https://github.com/go-i2p/go-i2p.git
synced 2025-06-07 18:24:25 -04:00
402 lines
13 KiB
Go
402 lines
13 KiB
Go
package i2np
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
common "github.com/go-i2p/go-i2p/lib/common/data"
|
|
"github.com/go-i2p/go-i2p/lib/common/session_key"
|
|
"github.com/go-i2p/go-i2p/lib/common/session_tag"
|
|
)
|
|
|
|
/*
|
|
I2P I2NP DatabaseLookup
|
|
https://geti2p.net/spec/i2np#databaselookup
|
|
Accurate for version 0.9.65
|
|
|
|
+----+----+----+----+----+----+----+----+
|
|
| SHA256 hash as the key to look up |
|
|
+ +
|
|
| |
|
|
+ +
|
|
| |
|
|
+ +
|
|
| |
|
|
+----+----+----+----+----+----+----+----+
|
|
| SHA256 hash of the routerInfo |
|
|
+ who is asking or the gateway to +
|
|
| send the reply to |
|
|
+ +
|
|
| |
|
|
+ +
|
|
| |
|
|
+----+----+----+----+----+----+----+----+
|
|
|flag| reply_tunnelId | size | |
|
|
+----+----+----+----+----+----+----+ +
|
|
| SHA256 of key1 to exclude |
|
|
+ +
|
|
| |
|
|
+ +
|
|
| |
|
|
+ +----+
|
|
| | |
|
|
+----+----+----+----+----+----+----+ +
|
|
| SHA256 of key2 to exclude |
|
|
+ +
|
|
~ ~
|
|
+ +----+
|
|
| | |
|
|
+----+----+----+----+----+----+----+ +
|
|
| |
|
|
+ +
|
|
| Session key if reply encryption |
|
|
+ was requested +
|
|
| |
|
|
+ +----+
|
|
| |tags|
|
|
+----+----+----+----+----+----+----+----+
|
|
| |
|
|
+ +
|
|
| Session tags if reply encryption |
|
|
+ was requested +
|
|
| |
|
|
+ +
|
|
| |
|
|
+----+----+----+----+----+----+----+----+
|
|
|
|
key ::
|
|
32 bytes
|
|
SHA256 hash of the object to lookup
|
|
|
|
from ::
|
|
32 bytes
|
|
if deliveryFlag == 0, the SHA256 hash of the routerInfo entry this
|
|
request came from (to which the reply should be
|
|
sent)
|
|
if deliveryFlag == 1, the SHA256 hash of the reply tunnel gateway (to
|
|
which the reply should be sent)
|
|
|
|
flags ::
|
|
1 byte
|
|
bit order: 76543210
|
|
bit 0: deliveryFlag
|
|
0 => send reply directly
|
|
1 => send reply to some tunnel
|
|
bit 1: encryptionFlag
|
|
through release 0.9.5, must be set to 0
|
|
as of release 0.9.6, ignored
|
|
as of release 0.9.7:
|
|
0 => send unencrypted reply
|
|
1 => send AES encrypted reply using enclosed key and tag
|
|
bits 3-2: lookup type flags
|
|
through release 0.9.5, must be set to 00
|
|
as of release 0.9.6, ignored
|
|
as of release 0.9.16:
|
|
00 => normal lookup, return RouterInfo or LeaseSet or
|
|
DatabaseSearchReplyMessage
|
|
Not recommended when sending to routers
|
|
with version 0.9.16 or higher.
|
|
01 => LS lookup, return LeaseSet or
|
|
DatabaseSearchReplyMessage
|
|
As of release 0.9.38, may also return a
|
|
LeaseSet2, MetaLeaseSet, or EncryptedLeaseSet.
|
|
10 => RI lookup, return RouterInfo or
|
|
DatabaseSearchReplyMessage
|
|
11 => exploration lookup, return DatabaseSearchReplyMessage
|
|
containing non-floodfill routers only (replaces an
|
|
excludedPeer of all zeroes)
|
|
bit 4: ECIESFlag
|
|
before release 0.9.46 ignored
|
|
as of release 0.9.46:
|
|
0 => send unencrypted or ElGamal reply
|
|
1 => send ChaCha/Poly encrypted reply using enclosed key
|
|
(whether tag is enclosed depends on bit 1)
|
|
bits 7-5:
|
|
through release 0.9.5, must be set to 0
|
|
as of release 0.9.6, ignored, set to 0 for compatibility with
|
|
future uses and with older routers
|
|
|
|
reply_tunnelId ::
|
|
4 byte TunnelID
|
|
only included if deliveryFlag == 1
|
|
tunnelId of the tunnel to send the reply to, nonzero
|
|
|
|
size ::
|
|
2 byte Integer
|
|
valid range: 0-512
|
|
number of peers to exclude from the DatabaseSearchReplyMessage
|
|
|
|
excludedPeers ::
|
|
$size SHA256 hashes of 32 bytes each (total $size*32 bytes)
|
|
if the lookup fails, these peers are requested to be excluded
|
|
from the list in the DatabaseSearchReplyMessage.
|
|
if excludedPeers includes a hash of all zeroes, the request is
|
|
exploratory, and the DatabaseSearchReplyMessage is requested
|
|
to list non-floodfill routers only.
|
|
|
|
reply_key ::
|
|
32 byte key
|
|
see below
|
|
|
|
tags ::
|
|
1 byte Integer
|
|
valid range: 1-32 (typically 1)
|
|
the number of reply tags that follow
|
|
see below
|
|
|
|
reply_tags ::
|
|
one or more 8 or 32 byte session tags (typically one)
|
|
see below
|
|
|
|
|
|
ElG to ElG
|
|
|
|
reply_key ::
|
|
32 byte SessionKey big-endian
|
|
only included if encryptionFlag == 1 AND ECIESFlag == 0, only as of release 0.9.7
|
|
|
|
tags ::
|
|
1 byte Integer
|
|
valid range: 1-32 (typically 1)
|
|
the number of reply tags that follow
|
|
only included if encryptionFlag == 1 AND ECIESFlag == 0, only as of release 0.9.7
|
|
|
|
reply_tags ::
|
|
one or more 32 byte SessionTags (typically one)
|
|
only included if encryptionFlag == 1 AND ECIESFlag == 0, only as of release 0.9.7
|
|
|
|
|
|
ECIES to ElG
|
|
|
|
reply_key ::
|
|
32 byte ECIES SessionKey big-endian
|
|
only included if encryptionFlag == 0 AND ECIESFlag == 1, only as of release 0.9.46
|
|
|
|
tags ::
|
|
1 byte Integer
|
|
required value: 1
|
|
the number of reply tags that follow
|
|
only included if encryptionFlag == 0 AND ECIESFlag == 1, only as of release 0.9.46
|
|
|
|
reply_tags ::
|
|
an 8 byte ECIES SessionTag
|
|
only included if encryptionFlag == 0 AND ECIESFlag == 1, only as of release 0.9.46
|
|
|
|
*/
|
|
|
|
type DatabaseLookup struct {
|
|
Key common.Hash
|
|
From common.Hash
|
|
Flags byte
|
|
ReplyTunnelID [4]byte
|
|
Size int
|
|
ExcludedPeers []common.Hash
|
|
ReplyKey session_key.SessionKey
|
|
Tags int
|
|
ReplyTags []session_tag.SessionTag
|
|
}
|
|
|
|
var ERR_DATABASE_LOOKUP_NOT_ENOUGH_DATA = errors.New("not enough i2np database lookup data")
|
|
|
|
func ReadDatabaseLookup(data []byte) (DatabaseLookup, error) {
|
|
log.Debug("Reading DatabaseLookup")
|
|
databaseLookup := DatabaseLookup{}
|
|
|
|
length, key, err := readDatabaseLookupKey(data)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to read Key")
|
|
return databaseLookup, err
|
|
}
|
|
databaseLookup.Key = key
|
|
|
|
length, from, err := readDatabaseLookupFrom(length, data)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to read From")
|
|
return databaseLookup, err
|
|
}
|
|
databaseLookup.From = from
|
|
|
|
length, flags, err := readDatabaseLookupFlags(length, data)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to read Flags")
|
|
return databaseLookup, err
|
|
}
|
|
databaseLookup.Flags = flags
|
|
|
|
length, replyTunnelID, err := readDatabaseLookupReplyTunnelID(flags, length, data)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to read ReplyTunnelID")
|
|
return databaseLookup, err
|
|
}
|
|
databaseLookup.ReplyTunnelID = replyTunnelID
|
|
|
|
length, size, err := readDatabaseLookupSize(length, data)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to read Size")
|
|
return databaseLookup, err
|
|
}
|
|
databaseLookup.Size = size
|
|
|
|
length, excludedPeers, err := readDatabaseLookupExcludedPeers(length, data, size)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to read ExcludedPeers")
|
|
return databaseLookup, err
|
|
}
|
|
databaseLookup.ExcludedPeers = excludedPeers
|
|
|
|
length, reply_key, err := readDatabaseLookupReplyKey(length, data)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to read ReplyKey")
|
|
return databaseLookup, err
|
|
}
|
|
databaseLookup.ReplyKey = reply_key
|
|
|
|
length, tags, err := readDatabaseLookupTags(length, data)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to read Tags")
|
|
return databaseLookup, err
|
|
}
|
|
databaseLookup.Tags = tags
|
|
|
|
length, reply_tags, err := readDatabaseLookupReplyTags(length, data, tags)
|
|
if err != nil {
|
|
log.WithError(err).Error("Failed to read ReplyTags")
|
|
return databaseLookup, err
|
|
}
|
|
databaseLookup.ReplyTags = reply_tags
|
|
|
|
log.Debug("DatabaseLookup read successfully")
|
|
return databaseLookup, nil
|
|
}
|
|
|
|
func readDatabaseLookupKey(data []byte) (int, common.Hash, error) {
|
|
if len(data) < 32 {
|
|
return 0, common.Hash{}, ERR_DATABASE_LOOKUP_NOT_ENOUGH_DATA
|
|
}
|
|
|
|
key := common.Hash(data[:32])
|
|
log.WithFields(logrus.Fields{
|
|
"at": "i2np.readDatabaseLookupKey",
|
|
"key": key,
|
|
}).Debug("parsed_database_lookup_key")
|
|
return 32, key, nil
|
|
}
|
|
|
|
func readDatabaseLookupFrom(length int, data []byte) (int, common.Hash, error) {
|
|
if len(data) < length + 32 {
|
|
return length, common.Hash{}, ERR_DATABASE_LOOKUP_NOT_ENOUGH_DATA
|
|
}
|
|
|
|
from := common.Hash(data[length:length + 32])
|
|
log.WithFields(logrus.Fields{
|
|
"at": "i2np.database_lookup.readDatabaseLookupFrom",
|
|
"from": from,
|
|
}).Debug("parsed_database_lookup_from")
|
|
return length + 32, from, nil
|
|
}
|
|
|
|
func readDatabaseLookupFlags(length int, data []byte) (int, byte, error) {
|
|
if len(data) < length + 1 {
|
|
return length, byte(0), ERR_DATABASE_LOOKUP_NOT_ENOUGH_DATA
|
|
}
|
|
flags := data[length]
|
|
|
|
log.WithFields(logrus.Fields{
|
|
"at": "i2np.database_lookup.readDatabaseLookupFlags",
|
|
"flags": flags,
|
|
}).Debug("parsed_database_lookup_flags")
|
|
return length + 1, flags, nil
|
|
}
|
|
|
|
func readDatabaseLookupReplyTunnelID(flags byte, length int, data []byte) (int, [4]byte, error) {
|
|
if flags & 1 != 1 {
|
|
return length, [4]byte{}, nil
|
|
}
|
|
if len(data) < length + 4 {
|
|
return length, [4]byte{}, ERR_DATABASE_LOOKUP_NOT_ENOUGH_DATA
|
|
}
|
|
|
|
replyTunnelID := [4]byte(data[length:length + 4])
|
|
log.WithFields(logrus.Fields{
|
|
"at": "i2np.database_lookup.readDatabaseLookupReplyTunnelID",
|
|
"reply_tunnel_id": replyTunnelID,
|
|
}).Debug("parsed_database_lookup_reply_tunnel_id")
|
|
return length + 4, replyTunnelID, nil
|
|
}
|
|
|
|
func readDatabaseLookupSize(length int, data []byte) (int, int, error) {
|
|
if len(data) < length + 2 {
|
|
return length, 0, ERR_DATABASE_LOOKUP_NOT_ENOUGH_DATA
|
|
}
|
|
|
|
size := common.Integer(data[length:length + 2]).Int()
|
|
log.WithFields(logrus.Fields{
|
|
"at": "i2np.database_lookup.readDatabaseLookupSize",
|
|
"size": size,
|
|
}).Debug("parsed_database_lookup_size")
|
|
return length + 2, size, nil
|
|
}
|
|
|
|
func readDatabaseLookupExcludedPeers(length int, data []byte, size int) (int, []common.Hash, error) {
|
|
if len(data) < length + size * 32 {
|
|
return length, []common.Hash{}, ERR_DATABASE_LOOKUP_NOT_ENOUGH_DATA
|
|
}
|
|
var excludedPeers []common.Hash
|
|
for i := 0; i < size; i++ {
|
|
offset := length + i * 32
|
|
peer := common.Hash(data[offset:offset + 32])
|
|
excludedPeers = append(excludedPeers, peer)
|
|
}
|
|
|
|
log.WithFields(logrus.Fields{
|
|
"at": "i2np.database_lookup.readDatabaseLookupExcludedPeers",
|
|
"excluded_peers": excludedPeers,
|
|
}).Debug("parsed_database_lookup_excluded_peers")
|
|
return length + size * 32, excludedPeers, nil
|
|
}
|
|
|
|
func readDatabaseLookupReplyKey(length int, data []byte) (int, session_key.SessionKey, error) {
|
|
if len(data) < length + 32 {
|
|
return length, session_key.SessionKey{}, ERR_DATABASE_LOOKUP_NOT_ENOUGH_DATA
|
|
}
|
|
replyKey := session_key.SessionKey(data[length:length + 32])
|
|
|
|
log.WithFields(logrus.Fields{
|
|
"at": "i2np.database_lookup.readDatabaseLookupReplyKey",
|
|
"reply_key": replyKey,
|
|
}).Debug("parsed_database_lookup_reply_key")
|
|
return length + 32, replyKey, nil
|
|
}
|
|
|
|
func readDatabaseLookupTags(length int, data []byte) (int, int, error) {
|
|
if len(data) < length + 1 {
|
|
return length, 0, ERR_DATABASE_LOOKUP_NOT_ENOUGH_DATA
|
|
}
|
|
tags := int(data[length])
|
|
|
|
log.WithFields(logrus.Fields{
|
|
"at": "i2np.database_lookup.readDatabaseLookupTags",
|
|
"tags": tags,
|
|
}).Debug("parsed_database_lookup_tags")
|
|
return length + 1, tags, nil
|
|
}
|
|
|
|
func readDatabaseLookupReplyTags(length int, data []byte, tags int) (int, []session_tag.SessionTag, error) {
|
|
if len(data) < length + tags * 32 {
|
|
return length, []session_tag.SessionTag{}, ERR_DATABASE_LOOKUP_NOT_ENOUGH_DATA
|
|
}
|
|
var reply_tags []session_tag.SessionTag
|
|
for i := 0; i < tags; i++ {
|
|
offset := length + i * 32
|
|
tag := session_tag.SessionTag(data[offset:offset + 32])
|
|
reply_tags = append(reply_tags, tag)
|
|
}
|
|
|
|
log.WithFields(logrus.Fields{
|
|
"at": "i2np.database_lookup.readDatabaseLookupReplyTags",
|
|
"reply_tags": reply_tags,
|
|
}).Debug("parsed_database_lookup_reply_tags")
|
|
return length + tags * 32, reply_tags, nil
|
|
}
|