Tapir provides a framework for building Anonymous / metadata resistant Services
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BaseOnionService.go 4.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. package tor
  2. import (
  3. "crypto/rand"
  4. "cwtch.im/tapir"
  5. "cwtch.im/tapir/primitives"
  6. "encoding/base64"
  7. "errors"
  8. "git.openprivacy.ca/openprivacy/libricochet-go/connectivity"
  9. "git.openprivacy.ca/openprivacy/libricochet-go/log"
  10. "golang.org/x/crypto/ed25519"
  11. "sync"
  12. "time"
  13. )
  14. // BaseOnionService is a concrete implementation of the service interface over Tor onion services.
  15. type BaseOnionService struct {
  16. connections sync.Map
  17. acn connectivity.ACN
  18. id *primitives.Identity
  19. privateKey ed25519.PrivateKey
  20. ls connectivity.ListenService
  21. }
  22. // Init initializes a BaseOnionService with a given private key and identity
  23. // The private key is needed to initialize the Onion listen socket, ideally we could just pass an Identity in here.
  24. func (s *BaseOnionService) Init(acn connectivity.ACN, sk ed25519.PrivateKey, id *primitives.Identity) {
  25. // run add onion
  26. // get listen context
  27. s.acn = acn
  28. s.id = id
  29. s.privateKey = sk
  30. }
  31. // WaitForCapabilityOrClose blocks until the connection has the given capability or the underlying connection is closed
  32. // (through error or user action)
  33. func (s *BaseOnionService) WaitForCapabilityOrClose(cid string, name string) (tapir.Connection, error) {
  34. conn, err := s.GetConnection(cid)
  35. if err == nil {
  36. for {
  37. if conn.HasCapability(name) {
  38. return conn, nil
  39. }
  40. if conn.IsClosed() {
  41. return nil, errors.New("connection is closed")
  42. }
  43. time.Sleep(time.Millisecond * 200)
  44. }
  45. }
  46. return nil, err
  47. }
  48. // GetConnection returns a connection for a given hostname.
  49. func (s *BaseOnionService) GetConnection(hostname string) (tapir.Connection, error) {
  50. var conn tapir.Connection
  51. s.connections.Range(func(key, value interface{}) bool {
  52. connection := value.(tapir.Connection)
  53. if connection.Hostname() == hostname {
  54. if !connection.IsClosed() {
  55. conn = connection
  56. return false
  57. }
  58. }
  59. return true
  60. })
  61. if conn == nil {
  62. return nil, errors.New("no connection found")
  63. }
  64. return conn, nil
  65. }
  66. // Connect initializes a new outbound connection to the given peer, using the defined Application
  67. func (s *BaseOnionService) Connect(hostname string, app tapir.Application) (bool, error) {
  68. _, err := s.GetConnection(hostname)
  69. if err == nil {
  70. // Note: This check is not 100% reliable. And we may end up with two connections between peers
  71. // This can happen when a client connects to a server as the server is connecting to the client
  72. // Because at the start of the connection the server cannot derive the true hostname of the client until it
  73. // has auth'd
  74. // We mitigate this by performing multiple checks when Connect'ing
  75. return true, errors.New("already connected to " + hostname)
  76. }
  77. // connects to a remote server
  78. // spins off to a connection struct
  79. log.Debugf("Connecting to %v", hostname)
  80. conn, _, err := s.acn.Open(hostname)
  81. if err == nil {
  82. connectionID := s.getNewConnectionID()
  83. // Second check. If we didn't catch a double connection attempt before the Open we *should* catch it now because
  84. // the auth protocol is quick and Open over onion connections can take some time.
  85. // Again this isn't 100% reliable.
  86. _, err := s.GetConnection(hostname)
  87. if err == nil {
  88. conn.Close()
  89. return true, errors.New("already connected to " + hostname)
  90. }
  91. log.Debugf("Connected to %v [%v]", hostname, connectionID)
  92. s.connections.Store(connectionID, tapir.NewConnection(s.id, hostname, true, conn, app.NewInstance()))
  93. return true, nil
  94. }
  95. log.Debugf("Error connecting to %v %v", hostname, err)
  96. return false, err
  97. }
  98. func (s *BaseOnionService) getNewConnectionID() string {
  99. id := make([]byte, 10)
  100. rand.Read(id)
  101. connectionID := "connection-" + base64.StdEncoding.EncodeToString(id)
  102. return connectionID
  103. }
  104. // Listen starts a blocking routine that waits for incoming connections and initializes connections with them based
  105. // on the given Application.
  106. func (s *BaseOnionService) Listen(app tapir.Application) error {
  107. // accepts a new connection
  108. // spins off to a connection struct
  109. ls, err := s.acn.Listen(s.privateKey, 9878)
  110. s.ls = ls
  111. log.Debugf("Starting a service on %v ", ls.AddressFull())
  112. if err == nil {
  113. for {
  114. conn, err := s.ls.Accept()
  115. if err == nil {
  116. tempHostname := s.getNewConnectionID()
  117. log.Debugf("Accepted connection from %v", tempHostname)
  118. s.connections.Store(tempHostname, tapir.NewConnection(s.id, tempHostname, false, conn, app.NewInstance()))
  119. } else {
  120. log.Debugf("Error accepting connection %v", err)
  121. return err
  122. }
  123. }
  124. }
  125. log.Debugf("Error listening to connection %v", err)
  126. return err
  127. }
  128. // Shutdown closes the service and ensures that any connections are closed.
  129. func (s *BaseOnionService) Shutdown() {
  130. s.ls.Close()
  131. s.connections.Range(func(key, value interface{}) bool {
  132. connection := value.(tapir.Connection)
  133. connection.Close()
  134. return true
  135. })
  136. }