lightning-terminal/session/server.go
Elle Mouton 87bef069e3
session: add session ID to grpc metadata via context
Add grpc interceptors that inject an LNC session's ID into the context
as gRPC metadata. By injecting it as such, it will be transported over
the wire in any outgoing gRPC calls.

This lets us be sure that any session call sent to the RPCMiddleware
interceptor in LND will continue to be grouped along with the
appropriate session ID. This gives LND a way to send the metadata we
include back to LiT meaning that we will later on be able to extract the
session ID again.
2025-05-15 06:15:10 +02:00

165 lines
3.7 KiB
Go

package session
import (
"context"
"crypto/tls"
"fmt"
"sync"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightninglabs/lightning-node-connect/mailbox"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/keychain"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
)
type sessionID [33]byte
type GRPCServerCreator func(sessionID ID,
opts ...grpc.ServerOption) *grpc.Server
type mailboxSession struct {
server *grpc.Server
cancel fn.Option[context.CancelFunc]
wg sync.WaitGroup
quit chan struct{}
}
func newMailboxSession() *mailboxSession {
return &mailboxSession{
quit: make(chan struct{}),
}
}
func (m *mailboxSession) start(session *Session,
serverCreator GRPCServerCreator, authData []byte,
onUpdate func(ctx context.Context, id ID,
remote *btcec.PublicKey) error,
onNewStatus func(s mailbox.ServerStatus)) error {
tlsConfig := &tls.Config{}
if session.DevServer {
tlsConfig = &tls.Config{InsecureSkipVerify: true}
}
ecdh := &keychain.PrivKeyECDH{PrivKey: session.LocalPrivateKey}
ctx, cancel := context.WithCancel(context.Background())
m.cancel = fn.Some(cancel)
keys := mailbox.NewConnData(
ecdh, session.RemotePublicKey, session.PairingSecret[:],
authData, func(key *btcec.PublicKey) error {
return onUpdate(ctx, session.ID, key)
}, nil,
)
// Start the mailbox gRPC server.
mailboxServer, err := mailbox.NewServer(
session.ServerAddr, keys, onNewStatus,
grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 2 * time.Minute,
}),
)
if err != nil {
return err
}
noiseConn := mailbox.NewNoiseGrpcConn(keys)
m.server = serverCreator(session.ID, grpc.Creds(noiseConn))
m.wg.Add(1)
go m.run(mailboxServer)
return nil
}
func (m *mailboxSession) run(mailboxServer *mailbox.Server) {
defer m.wg.Done()
log.Infof("Mailbox RPC server listening on %s", mailboxServer.Addr())
if err := m.server.Serve(mailboxServer); err != nil {
log.Errorf("Unable to serve mailbox gRPC: %v", err)
}
}
func (m *mailboxSession) stop() {
m.cancel.WhenSome(func(fn context.CancelFunc) { fn() })
m.server.Stop()
close(m.quit)
m.wg.Wait()
}
type Server struct {
serverCreator GRPCServerCreator
activeSessions map[sessionID]*mailboxSession
activeSessionsMtx sync.Mutex
quit chan struct{}
}
func NewServer(serverCreator GRPCServerCreator) *Server {
return &Server{
serverCreator: serverCreator,
activeSessions: make(map[sessionID]*mailboxSession),
quit: make(chan struct{}),
}
}
func (s *Server) StartSession(session *Session, authData []byte,
onUpdate func(ctx context.Context, id ID,
remote *btcec.PublicKey) error,
onNewStatus func(s mailbox.ServerStatus)) (chan struct{}, error) {
s.activeSessionsMtx.Lock()
defer s.activeSessionsMtx.Unlock()
var id sessionID
copy(id[:], session.LocalPublicKey.SerializeCompressed())
_, ok := s.activeSessions[id]
if ok {
return nil, fmt.Errorf("session %x is already active", id[:])
}
sess := newMailboxSession()
s.activeSessions[id] = sess
return sess.quit, sess.start(
session, s.serverCreator, authData, onUpdate, onNewStatus,
)
}
func (s *Server) StopSession(localPublicKey *btcec.PublicKey) error {
s.activeSessionsMtx.Lock()
defer s.activeSessionsMtx.Unlock()
var id sessionID
copy(id[:], localPublicKey.SerializeCompressed())
_, ok := s.activeSessions[id]
if !ok {
return fmt.Errorf("session %x is not active", id[:])
}
s.activeSessions[id].stop()
delete(s.activeSessions, id)
return nil
}
func (s *Server) Stop() {
s.activeSessionsMtx.Lock()
defer s.activeSessionsMtx.Unlock()
for id, session := range s.activeSessions {
session.stop()
delete(s.activeSessions, id)
}
}