remove introquery start on dht key/value schema

This commit is contained in:
херетик
2023-05-13 10:26:14 +01:00
parent 0a8fc638ac
commit 39ff6ec8c8
11 changed files with 175 additions and 440 deletions

View File

@@ -35,7 +35,7 @@ type Hash [32]byte
// return hex.EncodeToString(h[:])
// }
func (h Hash) Based32String() string {
func (h Hash) String() string {
return enc(h[:])[:52]
}

View File

@@ -0,0 +1,51 @@
package advert
import (
"time"
"github.com/multiformats/go-multiaddr"
"git-indra.lan/indra-labs/indra/pkg/crypto"
)
// Peer is the root identity document for an Indra peer. It is indexed by the
// Identity field, its public key. The slices found below it are derived via
// concatenation of strings with the keys and hashing to generate a derived
// field index, used to search the DHT for matches.
//
// The data stored for Peer must be signed with the key claimed by the Identity.
// For hidden services the address fields are signed in the DHT by the hidden
// service from their introduction solicitation, and the index from the current
// set is given by the hidden service.
type Peer struct {
Identity crypto.PubBytes
RelayRate int
// Addresses - first is address, nil for hidden services,
// hidden services have more than one, 6 or more are kept active.
Addresses []*Address
Services []Service
}
// Address entries are stored with an index generated by the combination of the
// 8 bit form of the index from the Peer struct field, after the word "address/"
// followed by a forward slash, and then hashed. A zero entry can be used to
// mark the end for scanning, if the list shrinks.
type Address struct {
Index byte
multiaddr.Multiaddr
// Expiry is zero for relay's public address, and something for hidden
// services.
Expiry time.Time
crypto.SigBytes
}
// Service stores a specification for the fee rate and the service port, which
// must be a well known port to match with a type of service, eg 80 for web, 53
// for DNS, etc. These are also attached to the Peer entry via concatenating
// "service/" and for this a 16 bit value allowing use of the full port
// range. A zero entry can be used also to mark the end when shrinking the
// list to indicate when to stop scanning for more entries.
type Service struct {
Index uint16
Port, RelayRate int
}

View File

@@ -14,7 +14,6 @@ import (
"git-indra.lan/indra-labs/indra/pkg/engine/sess"
"git-indra.lan/indra-labs/indra/pkg/engine/sessions"
"git-indra.lan/indra-labs/indra/pkg/util/slice"
"git-indra.lan/indra-labs/indra/pkg/util/splice"
)
func (ng *Engine) SendExit(port uint16, msg slice.Bytes, id nonce.ID,
@@ -69,41 +68,10 @@ func (ng *Engine) SendHiddenService(id nonce.ID, key *crypto.Prv,
return
}
func (ng *Engine) SendIntroQuery(id nonce.ID, hsk *crypto.Pub,
alice, bob *sessions.Data, hook func(in *onions.Intro)) {
fn := func(id nonce.ID, ifc interface{}, b slice.Bytes) (e error) {
s := splice.Load(b, slice.NewCursor())
on := onions.Recognise(s)
if e = on.Decode(s); fails(e) {
return
}
var oni *onions.Intro
var ok bool
if oni, ok = on.(*onions.Intro); !ok {
return
}
hook(oni)
return
}
log.D.Ln("sending introquery")
hops := sess.StandardCircuit()
s := make(sessions.Sessions, len(hops))
s[2] = bob
s[5] = alice
se := ng.Manager.SelectHops(hops, s, "sendintroquery")
var c sessions.Circuit
copy(c[:], se)
o := onions.MakeIntroQuery(id, hsk, bob, alice, c, ng.KeySet)
res := PostAcctOnion(ng.Manager, o)
log.D.Ln(res.ID)
ng.Manager.SendWithOneHook(c[0].Node.AddrPort, res, fn, ng.Responses)
}
func (ng *Engine) SendMessage(mp *onions.Message, hook responses.Callback) (id nonce.ID) {
func (ng *Engine) SendHiddenMessage(mp *onions.Message, hook responses.Callback) (id nonce.ID) {
// Add another two hops for security against unmasking.
preHops := []byte{0, 1}
oo := ng.Manager.SelectHops(preHops, mp.Forwards[:], "sendmessage")
oo := ng.Manager.SelectHops(preHops, mp.Forwards[:], "sendhiddenmessage")
mp.Forwards = [2]*sessions.Data{oo[0], oo[1]}
o := onions.Skins{}.Message(mp, ng.KeySet)
res := PostAcctOnion(ng.Manager, o)

View File

@@ -182,119 +182,119 @@ out:
}
}
func TestEngine_SendIntroQuery(t *testing.T) {
log2.SetLogLevel(log2.Info)
var clients []*Engine
var e error
const nCircuits = 10
ctx, cancel := context.WithCancel(context.Background())
if clients, e = CreateNMockCircuits(nCircuits, nCircuits,
ctx); fails(e) {
t.Error(e)
t.FailNow()
}
client := clients[0]
// Start up the clients.
for _, v := range clients {
go v.Start()
}
var wg sync.WaitGroup
var counter atomic.Int32
quit := qu.T()
go func() {
for {
select {
case <-time.After(time.Second * 4):
quit.Q()
t.Error("MakeHiddenService test failed")
case <-quit:
for i := 0; i < int(counter.Load()); i++ {
wg.Done()
}
for _, v := range clients {
v.Shutdown()
}
return
}
}
}()
for i := 0; i < nCircuits*nCircuits/2; i++ {
wg.Add(1)
counter.Inc()
e = clients[0].BuyNewSessions(1000000, func() {
wg.Done()
counter.Dec()
})
if fails(e) {
wg.Done()
counter.Dec()
}
wg.Wait()
}
var idPrv *crypto.Prv
if idPrv, e = crypto.GeneratePrvKey(); fails(e) {
return
}
id := nonce.NewID()
introducerHops := client.Manager.GetSessionsAtHop(2)
var introducer *sessions.Data
if len(introducerHops) > 1 {
cryptorand.Shuffle(len(introducerHops), func(i, j int) {
introducerHops[i], introducerHops[j] = introducerHops[j], introducerHops[i]
})
}
introducer = introducerHops[0]
returnHops := client.Manager.GetSessionsAtHop(5)
var returner *sessions.Data
if len(returnHops) > 1 {
cryptorand.Shuffle(len(returnHops), func(i, j int) {
returnHops[i], returnHops[j] = returnHops[j],
returnHops[i]
})
}
returner = returnHops[0]
svc := &services.Service{
Port: 2345,
RelayRate: 43523,
Transport: transport.NewByteChan(64),
}
client.SendHiddenService(id, idPrv, time.Now().Add(time.Hour), returner,
introducer, svc, func(id nonce.ID, ifc interface{},
b slice.Bytes) (e error) {
log.I.S("hidden service callback", id, ifc, b.ToBytes())
return
})
log2.SetLogLevel(log2.Trace)
// Now query everyone for the intro.
idPub := crypto.DerivePub(idPrv)
peers := clients[1:]
log.D.Ln("client address", client.Manager.GetLocalNodeAddressString())
for i := range peers {
wg.Add(1)
counter.Inc()
log.T.Ln("peer", i)
if len(returnHops) > 1 {
cryptorand.Shuffle(len(returnHops), func(i, j int) {
returnHops[i], returnHops[j] = returnHops[j], returnHops[i]
})
}
if len(introducerHops) > 1 {
cryptorand.Shuffle(len(introducerHops), func(i, j int) {
introducerHops[i], introducerHops[j] = introducerHops[j], introducerHops[i]
})
}
client.SendIntroQuery(id, idPub, introducerHops[0], returnHops[0],
func(in *onions.Intro) {
wg.Done()
counter.Dec()
log.I.Ln("success",
in.ID, in.Key.ToBase32Abbreviated(), in.AddrPort)
})
wg.Wait()
}
quit.Q()
cancel()
}
// func TestEngine_SendIntroQuery(t *testing.T) {
// log2.SetLogLevel(log2.Info)
// var clients []*Engine
// var e error
// const nCircuits = 10
// ctx, cancel := context.WithCancel(context.Background())
// if clients, e = CreateNMockCircuits(nCircuits, nCircuits,
// ctx); fails(e) {
// t.Error(e)
// t.FailNow()
// }
// client := clients[0]
// // Start up the clients.
// for _, v := range clients {
// go v.Start()
// }
// var wg sync.WaitGroup
// var counter atomic.Int32
// quit := qu.T()
// go func() {
// for {
// select {
// case <-time.After(time.Second * 4):
// quit.Q()
// t.Error("MakeHiddenService test failed")
// case <-quit:
// for i := 0; i < int(counter.Load()); i++ {
// wg.Done()
// }
// for _, v := range clients {
// v.Shutdown()
// }
// return
// }
// }
// }()
// for i := 0; i < nCircuits*nCircuits/2; i++ {
// wg.Add(1)
// counter.Inc()
// e = clients[0].BuyNewSessions(1000000, func() {
// wg.Done()
// counter.Dec()
// })
// if fails(e) {
// wg.Done()
// counter.Dec()
// }
// wg.Wait()
// }
// var idPrv *crypto.Prv
// if idPrv, e = crypto.GeneratePrvKey(); fails(e) {
// return
// }
// id := nonce.NewID()
// introducerHops := client.Manager.GetSessionsAtHop(2)
// var introducer *sessions.Data
// if len(introducerHops) > 1 {
// cryptorand.Shuffle(len(introducerHops), func(i, j int) {
// introducerHops[i], introducerHops[j] = introducerHops[j], introducerHops[i]
// })
// }
// introducer = introducerHops[0]
// returnHops := client.Manager.GetSessionsAtHop(5)
// var returner *sessions.Data
// if len(returnHops) > 1 {
// cryptorand.Shuffle(len(returnHops), func(i, j int) {
// returnHops[i], returnHops[j] = returnHops[j],
// returnHops[i]
// })
// }
// returner = returnHops[0]
// svc := &services.Service{
// Port: 2345,
// RelayRate: 43523,
// Transport: transport.NewByteChan(64),
// }
// client.SendHiddenService(id, idPrv, time.Now().Add(time.Hour), returner,
// introducer, svc, func(id nonce.ID, ifc interface{},
// b slice.Bytes) (e error) {
// log.I.S("hidden service callback", id, ifc, b.ToBytes())
// return
// })
// log2.SetLogLevel(log2.Trace)
// // Now query everyone for the intro.
// idPub := crypto.DerivePub(idPrv)
// peers := clients[1:]
// log.D.Ln("client address", client.Manager.GetLocalNodeAddressString())
// for i := range peers {
// wg.Add(1)
// counter.Inc()
// log.T.Ln("peer", i)
// if len(returnHops) > 1 {
// cryptorand.Shuffle(len(returnHops), func(i, j int) {
// returnHops[i], returnHops[j] = returnHops[j], returnHops[i]
// })
// }
// if len(introducerHops) > 1 {
// cryptorand.Shuffle(len(introducerHops), func(i, j int) {
// introducerHops[i], introducerHops[j] = introducerHops[j], introducerHops[i]
// })
// }
// client.SendIntroQuery(id, idPub, introducerHops[0], returnHops[0],
// func(in *onions.Intro) {
// wg.Done()
// counter.Dec()
// log.I.Ln("success",
// in.ID, in.Key.ToBase32Abbreviated(), in.AddrPort)
// })
// wg.Wait()
// }
// quit.Q()
// cancel()
// }
func TestEngine_Message(t *testing.T) {
log2.SetLogLevel(log2.Trace)
@@ -429,7 +429,7 @@ func TestEngine_Message(t *testing.T) {
wg.Add(1)
counter.Inc()
var ms *onions.Message
client.SendMessage(&onions.Message{
client.SendHiddenMessage(&onions.Message{
Address: rd.Address,
ID: nonce.NewID(),
Re: rd.ID,
@@ -446,7 +446,7 @@ func TestEngine_Message(t *testing.T) {
wg.Wait()
wg.Add(1)
counter.Inc()
client.SendMessage(&onions.Message{
client.SendHiddenMessage(&onions.Message{
Address: ms.Address,
ID: nonce.NewID(),
Re: ms.ID,

View File

@@ -36,9 +36,7 @@ const (
PaymentChanBuffers = 8
)
// NewNode creates a new Node. The Node for a client's self should use true in
// the local parameter to not initialise the peer state ring buffers as it won't
// use them.
// NewNode creates a new Node.
func NewNode(addr *netip.AddrPort, keys *crypto.Keys, tpt tpt.Transport,
relayRate int) (n *Node, id nonce.ID) {

View File

@@ -10,8 +10,6 @@ import (
type MyIntros map[crypto.PubBytes]*Introduction
type KnownIntros map[crypto.PubBytes]*Intro
type LocalHiddenService struct {
Prv *crypto.Prv
CurrentIntros []*Intro
@@ -25,15 +23,13 @@ type Services map[crypto.PubBytes]*LocalHiddenService
type Hidden struct {
sync.Mutex
MyIntros
KnownIntros
Services
}
func NewHiddenrouting() *Hidden {
return &Hidden{
MyIntros: make(MyIntros),
KnownIntros: make(KnownIntros),
Services: make(Services),
MyIntros: make(MyIntros),
Services: make(Services),
}
}
@@ -72,12 +68,6 @@ out:
}
}
}
for i := range hr.KnownIntros {
if hr.KnownIntros[i].ID == id {
delete(hr.KnownIntros, i)
break
}
}
hr.Unlock()
}
@@ -96,11 +86,6 @@ func (hr *Hidden) FindCloakedHiddenService(key crypto.PubKey) (
return &i
}
}
for i := range hr.KnownIntros {
if crypto.Match(key, i) {
return &i
}
}
return
}
@@ -133,22 +118,6 @@ func (hr *Hidden) FindIntroductionUnsafe(
return
}
func (hr *Hidden) FindKnownIntro(key crypto.PubBytes) (intro *Intro) {
hr.Lock()
var ok bool
if intro, ok = hr.KnownIntros[key]; ok {
}
hr.Unlock()
return
}
func (hr *Hidden) FindKnownIntroUnsafe(key crypto.PubBytes) (intro *Intro) {
var ok bool
if intro, ok = hr.KnownIntros[key]; ok {
}
return
}
func (hr *Hidden) Delete(key crypto.PubBytes) (header *Introduction) {
hr.Lock()
var ok bool
@@ -159,16 +128,6 @@ func (hr *Hidden) Delete(key crypto.PubBytes) (header *Introduction) {
return
}
func (hr *Hidden) DeleteKnownIntro(key crypto.PubBytes) (
header *Introduction) {
hr.Lock()
var ok bool
if _, ok = hr.KnownIntros[key]; ok {
delete(hr.KnownIntros, key)
}
hr.Unlock()
return
}
func (hr *Hidden) AddIntro(pk *crypto.Pub, intro *Introduction) {
hr.Lock()

View File

@@ -5,14 +5,11 @@ import (
"reflect"
"time"
"github.com/gookit/color"
"git-indra.lan/indra-labs/indra/pkg/crypto"
"git-indra.lan/indra-labs/indra/pkg/crypto/nonce"
"git-indra.lan/indra-labs/indra/pkg/crypto/sha256"
"git-indra.lan/indra-labs/indra/pkg/engine/coding"
"git-indra.lan/indra-labs/indra/pkg/engine/magic"
"git-indra.lan/indra-labs/indra/pkg/engine/node"
"git-indra.lan/indra-labs/indra/pkg/engine/sess"
"git-indra.lan/indra-labs/indra/pkg/engine/sessions"
"git-indra.lan/indra-labs/indra/pkg/util/slice"
@@ -113,48 +110,6 @@ func (x *Intro) Handle(s *splice.Splice, p Onion, ng Ngin) (e error) {
valid := x.Validate()
if valid {
log.T.Ln(ng.Mgr().GetLocalNodeAddressString(), "validated intro", x.ID)
kb := x.Key.ToBytes()
if _, ok := ng.GetHidden().KnownIntros[x.Key.ToBytes()]; ok {
log.D.Ln(ng.Mgr().GetLocalNodeAddressString(), "already have intro")
ng.Pending().ProcessAndDelete(x.ID, &kb, s.GetAll())
ng.GetHidden().Unlock()
return
}
log.D.F("%s storing intro for %s %s",
ng.Mgr().GetLocalNodeAddressString(), x.Key.ToBase32Abbreviated(),
x.ID)
ng.GetHidden().KnownIntros[x.Key.ToBytes()] = x
var ok bool
if ok, e = ng.Pending().ProcessAndDelete(x.ID, &kb,
s.GetAll()); ok || fails(e) {
ng.GetHidden().Unlock()
log.D.Ln("deleted pending response", x.ID)
return
}
log.D.F("%s sending out intro to %s at %s to all known peers",
ng.Mgr().GetLocalNodeAddressString(), x.Key.ToBase32Abbreviated(),
color.Yellow.Sprint(x.AddrPort.String()))
sender := ng.Mgr().FindNodeByAddrPort(x.AddrPort)
nn := make(map[nonce.ID]*node.Node)
ng.Mgr().ForEachNode(func(n *node.Node) bool {
if n.ID != sender.ID {
nn[n.ID] = n
return true
}
return false
})
counter := 0
for i := range nn {
log.T.F("sending intro to %s", color.Yellow.Sprint(nn[i].AddrPort.
String()))
nn[i].Transport.Send(s.GetAll())
counter++
if counter < 2 {
continue
}
break
}
}
ng.GetHidden().Unlock()
return

View File

@@ -1,107 +0,0 @@
package onions
import (
"reflect"
"git-indra.lan/indra-labs/indra/pkg/crypto"
"git-indra.lan/indra-labs/indra/pkg/crypto/nonce"
"git-indra.lan/indra-labs/indra/pkg/crypto/sha256"
"git-indra.lan/indra-labs/indra/pkg/engine/coding"
"git-indra.lan/indra-labs/indra/pkg/engine/magic"
"git-indra.lan/indra-labs/indra/pkg/engine/sess"
"git-indra.lan/indra-labs/indra/pkg/engine/sessions"
"git-indra.lan/indra-labs/indra/pkg/util/splice"
)
const (
IntroQueryMagic = "intq"
IntroQueryLen = magic.Len + nonce.IDLen + crypto.PubKeyLen +
3*sha256.Len + nonce.IVLen*3
)
type IntroQuery struct {
ID nonce.ID
// Ciphers is a set of 3 symmetric ciphers that are to be used in their
// given order over the reply message from the service.
crypto.Ciphers
// Nonces are the nonces to use with the cipher when creating the
// encryption for the reply message,
// they are common with the crypts in the header.
crypto.Nonces
// Port identifies the type of service as well as being the port used by
// the service to be relayed to. Notice there is no IP address, this is
// because Indranet only forwards to exits of decentralised services
// also running on the same machine. This service could be a proxy, of
// course, if configured this way. This could be done by tunneling from
// a local Socks5 proxy into Indranet and the exit node also having
// this.
Key *crypto.Pub
Onion
}
func introQueryGen() coding.Codec { return &IntroQuery{} }
func init() { Register(IntroQueryMagic, introQueryGen) }
func (x *IntroQuery) Magic() string { return IntroQueryMagic }
func (x *IntroQuery) Len() int { return IntroQueryLen + x.Onion.Len() }
func (x *IntroQuery) Wrap(inner Onion) { x.Onion = inner }
func (x *IntroQuery) GetOnion() interface{} { return x }
func (x *IntroQuery) Encode(s *splice.Splice) (e error) {
log.T.S("encoding", reflect.TypeOf(x),
x.ID, x.Key, x.Ciphers, x.Nonces,
)
return x.Onion.Encode(s.
Magic(IntroQueryMagic).
ID(x.ID).Ciphers(x.Ciphers).Nonces(x.Nonces).
Pubkey(x.Key),
)
}
func (x *IntroQuery) Decode(s *splice.Splice) (e error) {
if e = magic.TooShort(s.Remaining(), IntroQueryLen-magic.Len,
IntroQueryMagic); fails(e) {
return
}
s.ReadID(&x.ID).ReadCiphers(&x.Ciphers).ReadNonces(&x.Nonces).
ReadPubkey(&x.Key)
return
}
func (x *IntroQuery) Handle(s *splice.Splice, p Onion, ng Ngin) (e error) {
ng.GetHidden().Lock()
log.D.Ln(ng.Mgr().GetLocalNodeAddressString(), "handling introquery", x.ID,
x.Key.ToBase32Abbreviated())
var ok bool
var il *Intro
if il, ok = ng.GetHidden().KnownIntros[x.Key.ToBytes()]; !ok {
// if the reply is zeroes the querant knows it needs to retry at a
// different relay
il = &Intro{}
ng.GetHidden().Unlock()
log.E.Ln("intro not known")
return
}
ng.GetHidden().Unlock()
iqr := Encode(il)
rb := FormatReply(GetRoutingHeaderFromCursor(s), x.Ciphers, x.Nonces,
iqr.GetAll())
switch on1 := p.(type) {
case *Crypt:
sess := ng.Mgr().FindSessionByHeader(on1.ToPriv)
if sess != nil {
in := sess.Node.RelayRate * s.Len() / 2
out := sess.Node.RelayRate * rb.Len() / 2
ng.Mgr().DecSession(sess.Header.Bytes, in+out, false, "introquery")
}
}
ng.HandleMessage(rb, x)
return
}
func (x *IntroQuery) Account(res *sess.Data, sm *sess.Manager, s *sessions.Data, last bool) (skip bool, sd *sessions.Data) {
res.ID = x.ID
res.Billable = append(res.Billable, s.Header.Bytes)
skip = true
return
}

View File

@@ -1,72 +0,0 @@
package onions
import (
"testing"
"git-indra.lan/indra-labs/indra/pkg/crypto"
"git-indra.lan/indra-labs/indra/pkg/crypto/nonce"
"git-indra.lan/indra-labs/indra/pkg/engine/coding"
"git-indra.lan/indra-labs/indra/pkg/engine/sessions"
log2 "git-indra.lan/indra-labs/indra/pkg/proc/log"
)
func TestOnionSkins_IntroQuery(t *testing.T) {
log2.SetLogLevel(log2.Trace)
log2.App.Store("")
var e error
prvs, pubs := crypto.GetCipherSet(t)
ciphers := crypto.GenCiphers(prvs, pubs)
prv1, _ := crypto.GetTwoPrvKeys(t)
pub1 := crypto.DerivePub(prv1)
n3 := crypto.Gen3Nonces()
ep := &ExitPoint{
Routing: &Routing{
Sessions: [3]*sessions.Data{},
Keys: prvs,
Nonces: n3,
},
ReturnPubs: pubs,
}
id := nonce.NewID()
on := Skins{}.
IntroQuery(id, crypto.DerivePub(prv1), ep).
End().Assemble()
s := Encode(on)
s.SetCursor(0)
var onc coding.Codec
if onc = Recognise(s); onc == nil {
t.Error("did not unwrap")
t.FailNow()
}
if e = onc.Decode(s); fails(e) {
t.Error("did not decode")
t.FailNow()
}
log.D.Ln(s)
var ex *IntroQuery
var ok bool
if ex, ok = onc.(*IntroQuery); !ok {
t.Error("did not unwrap expected type")
t.FailNow()
}
for i := range ex.Ciphers {
if ex.Ciphers[i] != ciphers[i] {
t.Errorf("cipher %d did not unwrap correctly", i)
t.FailNow()
}
}
for i := range ex.Nonces {
if ex.Nonces[i] != n3[i] {
t.Errorf("nonce %d did not unwrap correctly", i)
t.FailNow()
}
}
if !ex.Key.Equals(pub1) {
t.Error("HiddenService did not decode correctly")
t.FailNow()
}
if ex.ID != id {
t.Error("Keys did not decode correctly")
t.FailNow()
}
}

View File

@@ -13,6 +13,9 @@ type Introduction struct {
ReplyHeader
}
// GossipIntro -
// todo: this should update a peer subkey on DHT for
// introductions, as well as the link to the hidden service peer data
func GossipIntro(intro *Intro, sm *sess.Manager, c qu.C) {
log.D.F("propagating hidden service intro for %s",
intro.Key.ToBase32Abbreviated())

View File

@@ -198,16 +198,6 @@ func (o Skins) Intro(id nonce.ID, key *crypto.Prv, ap *netip.AddrPort,
return append(o, NewIntro(id, key, ap, expires))
}
func (o Skins) IntroQuery(id nonce.ID, hsk *crypto.Pub, exit *ExitPoint) Skins {
return append(o, &IntroQuery{
ID: id,
Ciphers: crypto.GenCiphers(exit.Keys, exit.ReturnPubs),
Nonces: exit.Nonces,
Key: hsk,
Onion: nop,
})
}
func (o Skins) Message(msg *Message, ks *crypto.KeySet) Skins {
return append(o.
ForwardCrypt(msg.Forwards[0], ks.Next(), nonce.New()).
@@ -338,16 +328,6 @@ func MakeHiddenService(in *Intro, alice, bob *sessions.Data,
RoutingHeader(headers.Return)
}
func MakeIntroQuery(id nonce.ID, hsk *crypto.Pub, alice, bob *sessions.Data,
c sessions.Circuit, ks *crypto.KeySet) Skins {
headers := GetHeaders(alice, bob, c, ks)
return Skins{}.
RoutingHeader(headers.Forward).
IntroQuery(id, hsk, headers.ExitPoint()).
RoutingHeader(headers.Return)
}
func MakeSession(id nonce.ID, s [5]*Session,
client *sessions.Data, hop []*node.Node, ks *crypto.KeySet) Skins {