ADNL UDP - 노드 간 통신
ADNL over UDP는 노드와 TON 컴포 넌트가 서로 통신하는 데 사용됩니다. DHT와 RLDP 같은 더 높은 수준의 TON 프로토콜이 작동하는 기반이 되는 낮은 수준의 프로토콜입니다. 이 글에서는 노드 간 기본 통신을 위한 UDP 기반 ADNL의 작동 방식을 알아보겠습니다.
ADNL over TCP와 달리 UDP 구현에서는 핸드셰이크가 다른 형태로 이루어지고 채널 형태의 추가 레이어가 사용됩니다. 하지만 다른 원리는 비슷합니다: 암호화 키는 우리의 개인 키와 미리 config에서 알고 있거나 다른 네트워크 노드로부터 받은 피어의 공개 키를 기반으로 생성됩니다.
ADNL의 UDP 버전에서는 이니시에이터가 'create channel' 메시지를 보내면 동시에 피어로부터 초기 데이터를 수신하면서 연결이 설정됩니다. 채널 키가 계산되고 채널 생성이 확인됩니다. 채널이 설정되면 이후의 통신은 그 안에서 계속됩니다.
패킷 구조와 통신
첫 번째 패킷
프로토콜의 작동 방식을 이해하기 위해 DHT 노드와의 연결 초기화와 서명된 주소 목록 가져오기를 분석해 보겠습니다.
global config의 dht.nodes
섹션에서 원하는 노드를 찾으세요. 예시:
{
"@type": "dht.node",
"id": {
"@type": "pub.ed25519",
"key": "fZnkoIAxrTd4xeBgVpZFRm5SvVvSx7eN3Vbe8c83YMk="
},
"addr_list": {
"@type": "adnl.addressList",
"addrs": [
{
"@type": "adnl.address.udp",
"ip": 1091897261,
"port": 15813
}
],
"version": 0,
"reinit_date": 0,
"priority": 0,
"expire_at": 0
},
"version": -1,
"signature": "cmaMrV/9wuaHOOyXYjoxBnckJktJqrQZ2i+YaY3ehIyiL3LkW81OQ91vm8zzsx1kwwadGZNzgq4hI4PCB/U5Dw=="
}
- ED25519 키
fZnkoIAxrTd4xeBgVpZFRm5SvVvSx7eN3Vbe8c83YMk
를 가져와서 base64에서 디코딩합니다 - IP 주소
1091897261
를 서비스를 사용하거나 리틀 엔디안 바이트로 변환하여 이해 가능한 형식으로 변환하면65.21.7.173
이 됩니다 - 포트와 결합하여
65.21.7.173:15813
을 얻고 UDP 연결을 설정합니다.
노드와 통신하기 위해 채널을 열고 정보를 얻고자 하며, 주요 작업으로 서명된 주소 목록을 받고자 합니다. 이를 위해 2개의 메시지를 생성할 것입니다. 첫 번째는 채널 생성입니다:
adnl.message.createChannel key:int256 date:int = adnl.Message
여기에는 key와 date 두 가지 매개변수가 있습니다. date로는 현재 unix 타임스탬프를 지정합니다. key의 경우 - 채널용으로 새로운 ED25519 개인+공개 키 쌍을 생성해야 합니다. 이는 공개 암호화 키 초기화에 사용됩니다. 메시지의 key
매개변수에 생성한 공개 키를 사용하고 개인 키는 일단 저장해 둡니다.
채워진 TL 구조를 직렬화하면 다음과 같이 됩니다:
bbc373e6 -- TL ID adnl.message.createChannel
d59d8e3991be20b54dde8b78b3af18b379a62fa30e64af361c75452f6af019d7 -- key
555c8763 -- date
다음으로 메인 쿼리 - 주소 목 록 가져오기로 넘어갑니다. 실행하려면 먼저 TL 구조를 직렬화해야 합니다:
dht.getSignedAddressList = dht.Node
매개변수가 없으므로 ID만 직렬화하면 됩니다 - ed4879a9
.
다음으로 이것이 DHT 프로토콜의 더 높은 레벨 요청이므로 먼저 adnl.message.query
TL 구조로 래핑해야 합니다:
adnl.message.query query_id:int256 query:bytes = adnl.Message
query_id
로 무작위 32바이트를 생성하고, query
로는 바이트 배열로 래핑된 메인 요청을 사용합니다.
다음과 같이 됩니다:
7af98bb4 -- TL ID adnl.message.query
d7be82afbc80516ebca39784b8e2209886a69601251571444514b7f17fcd8875 -- query_id
04 ed4879a9 000000 -- query
패킷 구축
모든 통신은 TL 구조의 내용을 가진 패킷을 통해 이루어집니다:
adnl.packetContents
rand1:bytes -- random 7 or 15 bytes
flags:# -- bit flags, used to determine the presence of fields further
from:flags.0?PublicKey -- sender's public key
from_short:flags.1?adnl.id.short -- sender's ID
message:flags.2?adnl.Message -- message (used if there is only one message)
messages:flags.3?(vector adnl.Message) -- messages (if there are > 1)
address:flags.4?adnl.addressList -- list of our addresses
priority_address:flags.5?adnl.addressList -- priority list of our addresses
seqno:flags.6?long -- packet sequence number
confirm_seqno:flags.7?long -- sequence number of the last packet received
recv_addr_list_version:flags.8?int -- address version
recv_priority_addr_list_version:flags.9?int -- priority address version
reinit_date:flags.10?int -- connection reinitialization date (counter reset)
dst_reinit_date:flags.10?int -- connection reinitialization date from the last received packet
signature:flags.11?bytes -- signature
rand2:bytes -- random 7 or 15 bytes
= adnl.PacketContents
보내려는 모든 메시지를 직렬화했으 니 패킷 구축을 시작할 수 있습니다. 채널로 보내는 패킷은 채널 초기화 전에 보내는 패킷과 내용이 다릅니다. 먼저 초기화에 사용되는 메인 패킷을 분석해 보겠습니다.
초기 데이터 교환 중에는 채널 외부에서 직렬화된 패킷 내용 구조 앞에 피어의 공개 키 32바이트가 붙습니다. 우리의 공개 키 32바이트, 직렬화된 TL 패킷 내용 구조의 sha256 해시 32바이트. 패킷의 내용은 우리의 개인 키와 서버의 공개 키로부터 얻은 공유 키를 사용하여 암호화됩니다.
패킷 내용 구조를 직렬화하고 바이트별로 파싱해 보겠습니다:
89cd42d1 -- TL ID adnl.packetContents
0f 4e0e7dd6d0c5646c204573bc47e567 -- rand1, 15 (0f) random bytes
d9050000 -- flags (0x05d9) -> 0b0000010111011001
-- from (present because flag's zero bit = 1)
c6b41348 -- TL ID pub.ed25519
afc46336dd352049b366c7fd3fc1b143a518f0d02d9faef896cb0155488915d6 -- key:int256
-- messages (present because flag's third bit = 1)
02000000 -- vector adnl.Message, size = 2 messages
bbc373e6 -- TL ID adnl.message.createChannel
d59d8e3991be20b54dde8b78b3af18b379a62fa30e64af361c75452f6af019d7 -- key
555c8763 -- date (date of creation)
7af98bb4 -- TL ID [adnl.message.query](/)
d7be82afbc80516ebca39784b8e2209886a69601251571444514b7f17fcd8875 -- query_id
04 ed4879a9 000000 -- query (bytes size 4, padding 3)
-- address (present because flag's fourth bit = 1), without TL ID since it is specified explicitly
00000000 -- addrs (empty vector, because we are in client mode and do not have an address on wiretap)
555c8763 -- version (usually initialization date)
555c8763 -- reinit_date (usually initialization date)
00000000 -- priority
00000000 -- expire_at
0100000000000000 -- seqno (present because flag's sixth bit = 1)
0000000000000000 -- confirm_seqno (present because flag's seventh bit = 1)
555c8763 -- recv_addr_list_version (present because flag's eighth bit = 1, usually initialization date)
555c8763 -- reinit_date (present because flag's tenth bit = 1, usually initialization date)
00000000 -- dst_reinit_date (present because flag's tenth bit = 1)
0f 2b6a8c0509f85da9f3c7e11c86ba22 -- rand2, 15 (0f) random bytes
직렬화 후 - 이전에 생성하고 저장한 우리의 클라이언트(채널이 아닌) ED25519 개인 키로 결과 바이트 배열에 서명해야 합니다. 서명(64바이트 크기)을 생성한 후에는 패킷에 추가하고 다시 직렬화해야 하는데, 이번에는 서명의 존재를 의미하는 11번째 비트를 플래그에 추가합니다:
89cd42d1 -- TL ID adnl.packetContents
0f 4e0e7dd6d0c5646c204573bc47e567 -- rand1, 15 (0f) random bytes
d90d0000 -- flags (0x0dd9) -> 0b0000110111011001
-- from (present because flag's zero bit = 1)
c6b41348 -- TL ID pub.ed25519
afc46336dd352049b366c7fd3fc1b143a518f0d02d9faef896cb0155488915d6 -- key:int256
-- messages (present because flag's third bit = 1)
02000000 -- vector adnl.Message, size = 2 message
bbc373e6 -- TL ID adnl.message.createChannel
d59d8e3991be20b54dde8b78b3af18b379a62fa30e64af361c75452f6af019d7 -- key
555c8763 -- date (date of creation)
7af98bb4 -- TL ID adnl.message.query
d7be82afbc80516ebca39784b8e2209886a69601251571444514b7f17fcd8875 -- query_id
04 ed4879a9 000000 -- query (bytes size 4, padding 3)
-- address (present because flag's fourth bit = 1), without TL ID since it is specified explicitly
00000000 -- addrs (empty vector, because we are in client mode and do not have an address on wiretap)
555c8763 -- version (usually initialization date)
555c8763 -- reinit_date (usually initialization date)
00000000 -- priority
00000000 -- expire_at
0100000000000000 -- seqno (present because flag's sixth bit = 1)
0000000000000000 -- confirm_seqno (present because flag's seventh bit = 1)
555c8763 -- recv_addr_list_version (present because flag's eighth bit = 1, usually initialization date)
555c8763 -- reinit_date (present because flag's tenth bit = 1, usually initialization date)
00000000 -- dst_reinit_date (present because flag's tenth bit = 1)
40 b453fbcbd8e884586b464290fe07475ee0da9df0b8d191e41e44f8f42a63a710 -- signature (present because flag's eleventh bit = 1), (bytes size 64, padding 3)
341eefe8ffdc56de73db50a25989816dda17a4ac6c2f72f49804a97ff41df502 --
000000 --
0f 2b6a8c0509f85da9f3c7e11c86ba22 -- rand2, 15 (0f) random bytes