Запуск узла Hidden Lake на языке Go

от автора

Введение

Анонимная сеть Hidden Lake является в своей области уникальным и достаточно своеобразным проектом, т.к. базируется на совершенно иных методах и подходах анонимизации трафика, чем большинство ныне нам известных сетей. Из-за того, что сеть является относительно новой — она часто дополняется и совершенствуется. Одним из таковых дополнений стал новый способ запуска узла HL.

Немного о сети Hidden Lake

Анонимная сеть Hidden Lake (HL) — это децентрализованная F2F (friend-to-friend) анонимная сеть с теоретической доказуемостью. В отличие от известных анонимных сетей, подобия Tor, I2P, Mixminion, Crowds и т.п., сеть HL способна противостоять атакам глобального наблюдателя. Сети Hidden Lake для анонимизации своего трафика не важны такие критерии как: 1) уровень сетевой централизации, 2) количество узлов, 3) расположение узлов и 4) связь между узлами в сети.

Теоретическая доказуемость сети HL сводится к QB-задаче (задаче на базе очередей). Данная задача может быть описана следующим списком действий:

  1. Каждое сообщение m шифруется ключом получателя k: c = Ek(m),

  2. Сообщение c отправляется в период = T всем участникам сети,

  3. Период T одного участника независим от периодов T1, T2, …, Tn других участников,

  4. Если на период T сообщения не существует, то в сеть отправляется ложное сообщение v без получателя (со случайным ключом r): c = Er(v),

  5. Каждый участник пытается расшифровать принятое им сообщение из сети: m = Dk(c).

    QB-сеть с тремя узлами A, B, C

    QB-сеть с тремя узлами A, B, C

При такой модели глобальный наблюдатель будет видеть лишь факт генерации шифртекстов C = {c1, c2, …, cn} в определённо заданные периоды времени = T без возможности дальнейшего различия истинности Ek(m) или ложности Er(v) выбираемых им шифртекстов.

Более подробный анализ анонимной сети Hidden Lake, а также QB-задачи, можно найти в исследовательской работе: Анонимная сеть «Hidden Lake».

Возвращаемся к проблеме

Ранее существовало два способа того, как можно поднять узел Hidden Lake — либо использовать низкоуровневые функции и методы из пакета go-peer, чтобы с нуля сконструировать полноценную ноду, либо использовать высокоуровневый подход и запускать готовое приложение HLS (сервис анонимизации трафика), которое будет общаться с прикладными сервисами.

Для большинства случаев, когда нужно просто использовать функциональность сети — второй вариант конечно является наиболее релевантным. Тем не менее, когда хочется что-либо запрограммировать, проверить или протестировать, так чтобы, это ещё и успешно работало с сетью, то использование связки сервисов во главе с HLS будет являться избыточным, особенно когда нужно запрограммировать что-либо на родном языке Hidden Lake — языке Go. С другой стороны, использование пакета go-peer для реконструкции уже существующего и полностью работающего механизма HLS является ещё куда более нежелательным и избыточным способом.

Таким образом, перенос функциональности HLS в экспортируемую область позволит решить данное противоречие. И в этой статье мы посмотрим как стало возможно работать с сетью Hidden Lake средствами языка Go после переноса этой самой функциональности.

Низкоуровневый подход (go-peer)

Чтобы успешно связаться с другим узлом, посредством примитивов пакета go-peer, требуется сконструировать собственный узел как из деталек лего. Так например, чтобы создать экземпляр полноценного узла Hidden Lake, требуется написать примерно следующее:

Конструктор узла
networkMask := uint32(0x5f67705f) msgSizeBytes := uint64(8 << 10) netMsgSettings := net_message.NewSettings(&net_message.SSettings{     FWorkSizeBits: 18,     FNetworkKey:   "oi4r9NW9Le7fKF9d", }) node := anonymity.NewNode(     anonymity.NewSettings(&anonymity.SSettings{         FNetworkMask:  networkMask,         FServiceName:  "service",         FFetchTimeout: time.Minute,     }),     logger.NewLogger( logger.NewSettings(&logger.SSettings{             FInfo: os.Stdout,     FWarn: os.Stdout,             FErro: os.Stderr,         }), func(pLogArg logger.ILogArg) string {     logFactory, ok := pLogArg.(anon_logger.ILogGetterFactory)     if !ok {     panic("got invalid log arg")     }     logGetter := logFactory.Get()             return encoding.HexEncode(logGetter.GetHash())     }, ),     func() database.IKVDatabase {         db, err := database.NewKVDatabase(dbPath)     if err != nil {     panic(err)     }         return db     }(),     network.NewNode(         network.NewSettings(&network.SSettings{             FAddress:      "localhost:9571",             FMaxConnects:  256,             FReadTimeout:  5*time.Second,             FWriteTimeout: 5*time.Second,             FConnSettings: conn.NewSettings(&conn.SSettings{                 FMessageSettings: netMsgSettings,                 FLimitMessageSizeBytes: msgSizeBytes,                 FWaitReadTimeout:       time.Hour,                 FDialTimeout:           5*time.Second,                 FReadTimeout:           5*time.Second,                 FWriteTimeout:          5*time.Second,             }),         }),         cache.NewLRUCache(2 << 10),     ),     queue.NewQBProblemProcessor(         queue.NewSettings(&queue.SSettings{             FMessageConstructSettings: net_message.NewConstructSettings(&net_message.SConstructSettings{                 FSettings: netMsgSettings,                 FParallel: 1,             }),             FNetworkMask: networkMask,             FQueuePeriod: 5*time.Second,             FPoolCapacity: [2]uint64{256, 32},         }),         func() client.IClient {             client := client.NewClient(asymmetric.NewPrivKey(), msgSizeBytes)             if client.GetPayloadLimit() <= encoding.CSizeUint64 {                 panic(`client.GetPayloadLimit() <= encoding.CSizeUint64`)             }             return client         }(),     ),     asymmetric.NewMapPubKeys(), ).HandleFunc(     0x5f686c5f,     func( pCtx context.Context, pNode anonymity.INode, pSender asymmetric.IPubKey, pReqBytes []byte, ) ([]byte, error) { loadReq, err := request.LoadRequest(pReqBytes) if err != nil { return nil, errors.Join(ErrLoadRequest, err) } service, ok := servicesMap[loadReq.GetHost()] if !ok { return nil, ErrUndefinedService } pushReq, err := http.NewRequestWithContext( pCtx, loadReq.GetMethod(), fmt.Sprintf("http://%s%s", service, loadReq.GetPath()), bytes.NewReader(loadReq.GetBody()), ) if err != nil { return nil, errors.Join(ErrBuildRequest, err) } for key, val := range loadReq.GetHead() { pushReq.Header.Set(key, val) } pushReq.Header.Set(hls_settings.CHeaderPublicKey, pSender.ToString()) httpClient := &http.Client{Timeout: time.Minute} resp, err := httpClient.Do(pushReq) if err != nil { logger.PushWarn(logBuilder.WithType(internal_anon_logger.CLogWarnRequestToService)) return nil, errors.Join(ErrBadRequest, err) } defer resp.Body.Close() respMode := resp.Header.Get(hls_settings.CHeaderResponseMode) switch respMode { case "", hls_settings.CHeaderResponseModeON: return response.NewResponse(resp.StatusCode). WithHead(getResponseHead(resp)). WithBody(getResponseBody(resp)). ToBytes(), nil case hls_settings.CHeaderResponseModeOFF: return nil, nil default: return nil, ErrInvalidResponseMode } }, ) connKeeper := connkeeper.NewConnKeeper(     connkeeper.NewSettings(&connkeeper.SSettings{         FDuration:    10*time.Second,         FConnections: func() []string{           return getConnections()         },     }),     node.GetNetworkNode(), ) ...

Если человек не знаком с Hidden Lake, то такая простыня кода его скорее отпугнёт, чем заинтересует. Поэтому существует, как минимум, ещё один — высокоуровневый подход.

Высокоуровневый подход (HLS)

С высокоуровневым подходом всё куда проще — нужно лишь установить HLS (сервис анонимизации) и далее запустить его с ключом сети.

$ go install github.com/number571/hidden-lake/cmd/hls@latest $ hls -network=oi4r9NW9Le7fKF9d

Данный подход работает потому как в репозитории проекта Hidden Lake по умолчанию вшит список работающих ретрансляторов. Даже если ретрансляторы в будущем будут скомпрометированы, то из-за применяемой QB-задачи ничего плохого не произойдёт — анонимизация трафика нарушена не будет.

Среднеуровневый подход

Новый подход позволяет избавиться от излишних подробностей низкоуровневого подхода (настроек и конструктов) и от необходимости использования сервисов в высокоуровневом подходе. Иными словами, данный подход идеален, когда: 1) нужно запрограммировать что-либо конкретно связанное с сетью Hidden Lake без использования микросервисной архитектуры, 2) нет сил, возможности или желания разбираться в низкоуровневых деталях работы. Получаем в итоге своеобразное API.

Конструктор узла
node := network.NewHiddenLakeNode(     const networkKey = "oi4r9NW9Le7fKF9d"     network.NewSettingsByNetworkKey(networkKey, nil),     asymmetric.NewPrivKey(),     func() database.IKVDatabase {         kv, err := database.NewKVDatabase(dbPath + ".db")         if err != nil {             panic(err)         }         return kv     }(),     func() []string {         network := hiddenlake.GNetworks[networkKey]         conns := make([]string, 0, len(network.FConnections))         for _, c := range network.FConnections {             conns = append(conns, fmt.Sprintf("%s:%d", c.FHost, c.FPort))         }         return conns     },     func(_ context.Context, _ asymmetric.IPubKey, r request.IRequest) (response.IResponse, error) {         rsp := []byte(fmt.Sprintf("echo: %s", string(r.GetBody())))         return response.NewResponse().WithBody(rsp), nil     }, ) ...

Проверяем работу

Всё, что нам остаётся — это проверить работу нового подхода на практике. Для этого достаточно будет запустить два узла, которые будут между собой как-либо взаимодействовать. Одним из наиболее простых способов взаимодействия является реализация echo-сервиса, при котором один узел будет отправлять запрос, а другой соответственно отвечать.

package main  import ( ... )  func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel()      // Запускаем два узла var ( node1 = runNode(ctx, "node1") node2 = runNode(ctx, "node2") )      // Узлы обмениваются публичными ключами _, pubKey := exchangeKeys(node1, node2)  for {         // Запрос с ожиданием ответа rsp, err := node1.FetchRequest( ctx, pubKey, request.NewRequest().WithBody([]byte("hello, world!")), ) if err != nil { fmt.Println("error:(%s)\n", err.Error()) continue } fmt.Printf("response:(%s)\n", string(rsp.GetBody())) } }  ...
Функции runNode & exchangeKeys
func runNode(ctx context.Context, dbPath string) network.IHiddenLakeNode { const networkKey = "oi4r9NW9Le7fKF9d"    node := network.NewHiddenLakeNode(         // Задаём настройки узла исходя из ключа сети network.NewSettingsByNetworkKey(networkKey, nil),                // Приватный ключ - идентификатор узла asymmetric.NewPrivKey(),          // База данных нужна для сохранения хешей принимаемых шифртекстов func() database.IKVDatabase { kv, err := database.NewKVDatabase(dbPath + ".db") if err != nil { panic(err) } return kv }(),          // Подключаем все соединения из build/networks.yml по ключу сети func() []string { network := hiddenlake.GNetworks[networkKey] conns := make([]string, 0, len(network.FConnections)) for _, c := range network.FConnections { conns = append(conns, fmt.Sprintf("%s:%d", c.FHost, c.FPort)) } return conns },          // Пример запуска простого echo-сервиса func(_ context.Context, _ asymmetric.IPubKey, r request.IRequest) (response.IResponse, error) { rsp := []byte(fmt.Sprintf("echo: %s", string(r.GetBody()))) return response.NewResponse().WithBody(rsp), nil }, )      // Запускаем узел на      // 1. генерацию шифрованного трафика     // 2. подключение к ретрансляторам go func() { _ = node.Run(ctx) }() return node }
func exchangeKeys(hlNode1, hlNode2 network.IHiddenLakeNode) (asymmetric.IPubKey, asymmetric.IPubKey) { node1 := hlNode1.GetOrigNode() node2 := hlNode2.GetOrigNode()  pubKey1 := node1.GetMessageQueue().GetClient().GetPrivKey().GetPubKey() pubKey2 := node2.GetMessageQueue().GetClient().GetPrivKey().GetPubKey()  node1.GetMapPubKeys().SetPubKey(pubKey2) node2.GetMapPubKeys().SetPubKey(pubKey1)  return pubKey1, pubKey2 }

Запускаем и проверяем. Полный исходный код данного примера можно найти тут.

$ go run . > response:(echo: hello, world!) response:(echo: hello, world!) response:(echo: hello, world!) response:(echo: hello, world!) response:(echo: hello, world!) response:(echo: hello, world!) response:(echo: hello, world!)

Заключение

В результате, мы смогли запустить два полноценно работающих узла в сети Hidden Lake, обменивающихся между собой сообщениями без использования низкоуровневых примитивов пакета go-peer и высокоуровневого подхода связки HL сервисов.


ссылка на оригинал статьи https://habr.com/ru/articles/858110/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *