ADNL UDP - Düğümler Arası
ADNL üzerindeki UDP, düğümler ve TON bileşenlerinin birbirleriyle iletişim kurması için kullanılmaktadır. Bu, DHT ve RLDP gibi diğer, daha yüksek seviyeli TON protokollerinin çalıştığı düşük seviyeli bir protokoldür.
Bu makalede, düğümler arasında temel iletişim için ADNL'nin UDP üzerindeki işleyişini öğreneceğiz.
ADNL'nin TCP üzerindeki uygulamasının aksine, UDP uygulamasında el sıkışma farklı bir biçimde gerçekleşir ve ek bir katman şeklinde kanallar kullanılır ama diğer ilkeler benzerdir: şifreleme anahtarları da özel anahtarımız ve önceden yapılandırma dosyasından bilinen veya diğer ağ düğümlerinden alınan eşleşen düğümün genel anahtarına dayanarak oluşturulur.
ADNL'nin UDP versiyonunda, bağlantı başlatıcı 'kanal oluştur' mesajını gönderdiğinde, ilk verilerin alınmasıyla birlikte aynı anda kurulmaktadır; kanalın anahtarı hesaplanacak ve kanalın oluşturulması onaylanacaktır.
Kanal kurulduktan sonra, daha fazla iletişim kanalda devam edecektir.
Paket Yapısı ve İletişim
İlk Paketler
Protokolün nasıl çalıştığını anlamak için DHT düğümü ile bağlantının başlatılmasını ve onun adreslerinin imzalı listesinin alınmasını analiz edelim.
Beğendiğiniz düğümü global config içinde, dht.nodes
bölümünde bulabilirsiniz. Örneğin:
{
"@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=="
}
1. ED25519 anahtarını fZnkoIAxrTd4xeBgVpZFRm5SvVvSx7eN3Vbe8c83YMk
alalım ve base64'ten çözümleyelim.
2. IP adresini 1091897261
alalım ve anlaşılabilir bir formata dönüştürelim, hizmet kullanarak ya da little endian byte dönüşümüyle elde edeceğimiz sonuç 65.21.7.173
olacaktır.
3. Port ile birleştirerek 65.21.7.173:15813
elde edelim ve bir UDP bağlantısı kuralım.
Bir düğümle iletişim kurmak ve bazı bilgiler almak amacıyla bir kanal açmak istiyoruz ve ana görevimiz - ondan imzalı adreslerin bir listesini almak. Bunu yapmak için iki mesaj üreteceğiz, ilki - kanal oluştur:
adnl.message.createChannel key:int256 date:int = adnl.Message
Burada iki parametre var - anahtar ve tarih. Tarih olarak mevcut unix zaman damgasını belirteceğiz. Anahtar için ise - kanal için özel olarak yeni bir ED25519 özel+genel anahtar çifti oluşturmalıyız, bunlar kamusal şifreleme anahtarının
başlatılmasında kullanılacaktır. Mesajın key
parametresinde oluşturduğumuz genel anahtarı kullanacağız ve özel anahtarı şimdilik saklayacağız.
Doldurulmuş TL yapısını serialize edelim ve şunu elde edelim:
bbc373e6 -- TL ID adnl.message.createChannel
d59d8e3991be20b54dde8b78b3af18b379a62fa30e64af361c75452f6af019d7 -- key
555c8763 -- date
Sonraki adımda, ana sorgumuza geçelim - adreslerin listesini al.
Bunu gerçekleştirmek için önce TL yapısını serialize etmemiz gerekiyor:
dht.getSignedAddressList = dht.Node
Parametresi yok, bu yüzden sadece serialize ediyoruz. Sadece id'si ed4879a9
olacak.
Daha sonra, bu DHT protokolünün daha yüksek seviyeli bir isteği olduğundan, önce bunu bir adnl.message.query
TL yapısına sarmamız gerekiyor:
adnl.message.query query_id:int256 query:bytes = adnl.Message
query_id
olarak rastgele 32 byte üreteceğiz, query
olarak ise ana isteğimizi kullanacağız, byte dizisi olarak sarılmış
.
Sonunda şunu elde edeceğiz:
7af98bb4 -- TL ID adnl.message.query
d7be82afbc80516ebca39784b8e2209886a69601251571444514b7f17fcd8875 -- query_id
04 ed4879a9 000000 -- query
Paket Oluşturma
Tüm iletişim paketleri kullanılarak gerçekleştirilir, içeriği TL yapısı ile belirlenir:
adnl.packetContents
rand1:bytes -- rastgele 7 veya 15 byte
flags:# -- bit bayrakları, daha ileri alanların mevcudiyetini belirlemek için kullanılır
from:flags.0?PublicKey -- gönderenin genel anahtarı
from_short:flags.1?adnl.id.short -- gönderenin kimliği
message:flags.2?adnl.Message -- mesaj (sadece bir mesaj varsa kullanılır)
messages:flags.3?(vector adnl.Message) -- mesajlar (1'den fazla varsa)
address:flags.4?adnl.addressList -- adreslerimizin listesi
priority_address:flags.5?adnl.addressList -- öncelikli adreslerimizin listesi
seqno:flags.6?long -- paket sıralama numarası
confirm_seqno:flags.7?long -- alınan son paketin sıralama numarası
recv_addr_list_version:flags.8?int -- adres sürümü
recv_priority_addr_list_version:flags.9?int -- öncelikli adres sürümü
reinit_date:flags.10?int -- bağlantı yeniden başlatma tarihi (sayacın sıfırlanması)
dst_reinit_date:flags.10?int -- son alınan paket ile bağlantı yeniden başlatma tarihi
signature:flags.11?bytes -- imza
rand2:bytes -- rastgele 7 veya 15 byte
= adnl.PacketContents
Göndermek istediğimiz tüm mesajları seri hale getirdikten sonra, paketi oluşturmaya başlayabiliriz.
Bir kanala gönderilecek paketler, kanalın başlatılmasından önce gönderilen paketlerin içeriğinden farklıdır. Önce, başlatma için kullanılan ana paketi analiz edelim.
Başlangıç veri alışverişi sırasında, kanal dışında, paketin seri hale getirilmiş içerik yapısı eş tarafın genel anahtarı ile önden eklenir - 32 byte. Bizim genel anahtarımız 32 byte, paketin içerik yapısının seri hale getirilmiş TL'sinin sha256 hash'i - 32 byte. Paketin içeriği, özel anahtarımız ile sunucunun genel anahtarından elde edilen paylaşılan anahtar
kullanılarak şifrelenir.
Paket içerik yapımızı seri hale getirin ve byte byte ayrıştırın:
89cd42d1 -- TL ID adnl.packetContents
0f 4e0e7dd6d0c5646c204573bc47e567 -- rand1, 15 (0f) rastgele byte
d9050000 -- flags (0x05d9) -> 0b0000010111011001
-- from (sıfır bitinin 1 olması nedeniyle mevcut)
c6b41348 -- TL ID pub.ed25519
afc46336dd352049b366c7fd3fc1b143a518f0d02d9faef896cb0155488915d6 -- key:int256
-- mesajlar (üçüncü bitin 1 olması nedeniyle mevcut)
02000000 -- vector adnl.Message, boyut = 2 mesaj
bbc373e6 -- TL ID adnl.message.createChannel
d59d8e3991be20b54dde8b78b3af18b379a62fa30e64af361c75452f6af019d7 -- key
555c8763 -- tarih (oluşum tarihi)
7af98bb4 -- TL ID `adnl.message.query`
d7be82afbc80516ebca39784b8e2209886a69601251571444514b7f17fcd8875 -- query_id
04 ed4879a9 000000 -- sorgu (byte boyutu 4, dolgu 3)
-- adres (dördüncü bitin 1 olması nedeniyle mevcut), TL ID belirtilmediği için açıkça belirtilmiş
00000000 -- addrs (boş vektör, çünkü biz istemci modundayız ve tapede adresimiz yok)
555c8763 -- sürüm (genellikle başlangıç tarihi)
555c8763 -- reinit_date (genellikle başlangıç tarihi)
00000000 -- öncelik
00000000 -- expire_at
0100000000000000 -- seqno (altıncı bitin 1 olması nedeniyle mevcut)
0000000000000000 -- confirm_seqno (yedinci bitin 1 olması nedeniyle mevcut)
555c8763 -- recv_addr_list_version (sekizinci bitin 1 olması nedeniyle mevcut, genellikle başlangıç tarihi)
555c8763 -- reinit_date (onuncu bitin 1 olması nedeniyle mevcut, genellikle başlangıç tarihi)
00000000 -- dst_reinit_date (onuncu bitin 1 olması nedeniyle mevcut)
0f 2b6a8c0509f85da9f3c7e11c86ba22 -- rand2, 15 (0f) rastgele byte
Seri hale getirdikten sonra - elde edilen byte dizisini, daha önce ürettiğimiz özel istemci (kanalın değil) ED25519 anahtarı ile imzalamamız gerekiyor. İmza oluşturulduktan (64 byte boyutunda), pakete ekliyoruz, tekrar seri hale getiriyoruz, ama bu sefer bayraklara 11. biti ekliyoruz, bu da imzanın mevcut olduğunu gösterir:
89cd42d1 -- TL ID adnl.packetContents
0f 4e0e7dd6d0c5646c204573bc47e567 -- rand1, 15 (0f) rastgele byte
d90d0000 -- flags (0x0dd9) -> 0b0000110111011001
-- from (sıfır bitinin 1 olması nedeniyle mevcut)
c6b41348 -- TL ID pub.ed25519
afc46336dd352049b366c7fd3fc1b143a518f0d02d9faef896cb0155488915d6 -- key:int256
-- mesajlar (üçüncü bitin 1 olması nedeniyle mevcut)
02000000 -- vector adnl.Message, boyut = 2 mesaj
bbc373e6 -- TL ID adnl.message.createChannel
d59d8e3991be20b54dde8b78b3af18b379a62fa30e64af361c75452f6af019d7 -- key
555c8763 -- tarih (oluşum tarihi)
7af98bb4 -- TL ID adnl.message.query
d7be82afbc80516ebca39784b8e2209886a69601251571444514b7f17fcd8875 -- query_id
04 ed4879a9 000000 -- sorgu (byte boyutu 4, dolgu 3)
-- adres (dördüncü bitin 1 olması nedeniyle mevcut), TL ID belirtilmediği için açıkça belirtilmiş
00000000 -- addrs (boş vektör, çünkü biz istemci modundayız ve tapede adresimiz yok)
555c8763 -- sürüm (genellikle başlangıç tarihi)
555c8763 -- reinit_date (genellikle başlangıç tarihi)
00000000 -- öncelik
00000000 -- expire_at
0100000000000000 -- seqno (altıncı bitin 1 olması nedeniyle mevcut)
0000000000000000 -- confirm_seqno (yedinci bitin 1 olması nedeniyle mevcut)
555c8763 -- recv_addr_list_version (sekizinci bitin 1 olması nedeniyle mevcut, genellikle başlangıç tarihi)
555c8763 -- reinit_date (onuncu bitin 1 olması nedeniyle mevcut, genellikle başlangıç tarihi)
00000000 -- dst_reinit_date (onuncu bitin 1 olması nedeniyle mevcut)
40 b453fbcbd8e884586b464290fe07475ee0da9df0b8d191e41e44f8f42a63a710 -- imza (on birinci bitin 1 olması nedeniyle mevcut), (byte boyutu 64, dolgu 3)
341eefe8ffdc56de73db50a25989816dda17a4ac6c2f72f49804a97ff41df502 --
000000 --
0f 2b6a8c0509f85da9f3c7e11c86ba22 -- rand2, 15 (0f) rastgele byte
Artık bir araya getirilmiş, imzalanmış ve seri hale getirilmiş bir paketimiz var, bu bir byte dizisidir. Alıcı tarafından bütünlüğünün sonraki doğrulanması için, paketin sha256 hash'ini hesaplamamız gerekiyor. Örneğin, bu 408a2a4ed623b25a2e2ba8bbe92d01a3b5dbd22c97525092ac3203ce4044dcd2
olsun.
Şimdi paketimizin içeriğini, özel anahtarımız ile eş tarafın genel anahtarı kullanılarak elde edilen paylaşılan anahtar
ile AES-CTR şifreleme yöntemi ile şifreleyelim.
Göndermeye neredeyse hazırız, bir tek ED25519 eş taraf anahtarının ID'sini hesaplamak
ve her şeyi bir araya getirmek kaldı:
daa76538d99c79ea097a67086ec05acca12d1fefdbc9c96a76ab5a12e66c7ebb -- sunucu Anahtar ID'si
afc46336dd352049b366c7fd3fc1b143a518f0d02d9faef896cb0155488915d6 -- bizim genel anahtarımız
408a2a4ed623b25a2e2ba8bbe92d01a3b5dbd22c97525092ac3203ce4044dcd2 -- sha256 içerik hash'i (şifrelemeden önce)
... -- paketin şifrelenmiş içeriği
Artık inşa ettiğimiz paketi, UDP üzerinden eş tarafa gönderebiliriz ve bir yanıt bekleyebiliriz.
Yanıt olarak, yapısal olarak benzeyen ancak farklı mesajlar içeren bir paket alacağız. Bu aşağıdakilerden oluşacaktır:
68426d4906bafbd5fe25baf9e0608cf24fffa7eca0aece70765d64f61f82f005 -- bizim anahtarımızın ID'si
2d11e4a08031ad3778c5e060569645466e52bd1bd2c7b78ddd56def1cf3760c9 -- paylaşılan anahtar için sunucu genel anahtarı
f32fa6286d8ae61c0588b5a03873a220a3163cad2293a5dace5f03f06681e88a -- sha256 içerik hash'i (şifrelemeden önce)
... -- paketin şifrelenmiş içeriği
Sunucudan gelen paketin ayrıştırılması şöyle yapılır:
- Paketteki anahtar ID'sini kontrol ederiz, böylece paketin bize ait olduğunu anlayabiliriz.
- Paketteki sunucunun genel anahtarını ve bizim özel anahtarımızı kullanarak bir paylaşılan anahtar hesaplarız ve paketin içeriğini çözeriz.
- Gönderilen sha256 hash'indeki veriyi, çözülen veriden alınan hash ile karşılaştırırız, bunlar eşleşmelidir.
- Paket içeriğini
adnl.packetContents
TL şemasını kullanarak ayrıştırmaya başlarız.
Paketin içeriği şöyle görünecektir:
89cd42d1 -- TL ID adnl.packetContents
0f 985558683d58c9847b4013ec93ea28 -- rand1, 15 (0f) rastgele byte
ca0d0000 -- flags (0x0dca) -> 0b0000110111001010
daa76538d99c79ea097a67086ec05acca12d1fefdbc9c96a76ab5a12e66c7ebb -- from_short (birinci bitin 1 olması nedeniyle mevcut)
02000000 -- mesajlar (üçüncü bitin 1 olması nedeniyle mevcut)
691ddd60 -- TL ID adnl.message.confirmChannel
db19d5f297b2b0d76ef79be91ad3ae01d8d9f80fab8981d8ed0c9d67b92be4e3 -- anahtar (sunucu kanal genel anahtarı)
d59d8e3991be20b54dde8b78b3af18b379a62fa30e64af361c75452f6af019d7 -- peer_key (bizim genel kanal anahtarımız)
94848863 -- tarih
1684ac0f -- TL ID adnl.message.answer
d7be82afbc80516ebca39784b8e2209886a69601251571444514b7f17fcd8875 -- query_id
90 48325384c6b413487d99e4a08031ad3778c5e060569645466e52bd5bd2c7b -- yanıt (sorumuzun yanıtı, içeriğini DHT hakkında bir makalede analiz edeceğiz)
78ddd56def1cf3760c901000000e7a60d67ad071541c53d0000ee354563ee --
35456300000000000000009484886340d46cc50450661a205ad47bacd318c --
65c8fd8e8f797a87884c1bad09a11c36669babb88f75eb83781c6957bc976 --
6a234f65b9f6e7cc9b53500fbe2c44f3b3790f000000 --
000000 --
0100000000000000 -- seqno (altıncı bitin 1 olması nedeniyle mevcut)
0100000000000000 -- confirm_seqno (yedinci bitin 1 olması nedeniyle mevcut)
94848863 -- recv_addr_list_version (sekizinci bitin 1 olması nedeniyle mevcut, genellikle başlangıç tarihi)
ee354563 -- reinit_date (onuncu bitin 1 olması nedeniyle mevcut, genellikle başlangıç tarihi)
94848863 -- dst_reinit_date (onuncu bitin 1 olması nedeniyle mevcut)
40 5c26a2a05e584e9d20d11fb17538692137d1f7c0a1a3c97e609ee853ea9360ab6 -- imza (on birinci bitin 1 olması nedeniyle mevcut), (byte boyutu 64, dolgu 3)
d84263630fe02dfd41efb5cd965ce6496ac57f0e51281ab0fdce06e809c7901 --
000000 --
0f c3354d35749ffd088411599101deb2 -- rand2, 15 (0f) rastgele byte
Sunucu, bize adnl.message.confirmChannel
ve adnl.message.answer
olmak üzere iki mesaj ile yanıt verdi.
adnl.message.answer
ile her şey basit, bu bizim dht.getSignedAddressList
isteğimize verilen yanıttır, içeriğini DHT hakkında bir makalede analiz edeceğiz.
adnl.message.confirmChannel
'a odaklanalım, bu, eş tarafın kanalın oluşturulmasını onayladığı ve bize genel kanal anahtarını gönderdiği anlamına gelir. Şimdi, özel kanal anahtarımız ve eş tarafın genel kanal anahtarı ile paylaşılan anahtarı hesaplayabiliriz
.
Artık paylaşılan kanal anahtarını hesapladığımıza göre, bunu iki anahtara ayırmamız gerekiyor - birisi giden mesajları şifrelemek, diğeri gelen mesajları çözmek için. Bunu yapmak oldukça basit, ikinci anahtar paylaşılan anahtarın ters sırada yazılması ile elde edilir. Örnek:
Paylaşılan anahtar : AABB2233
Birinci anahtar: AABB2233
İkinci anahtar: 3322BBAA
Hangi anahtarın ne için kullanılacağını belirlemek için, kendi genel kanal anahtarımızın ID'sini sunucu kanalının genel anahtarının ID'si ile karşılaştırabiliriz, bunları sayısal forma - uint256'ya dönüştürerek. Bu yaklaşım, hem sunucu hem de istemcinin hangi anahtarın ne için kullanılacağını belirlemesini sağlamak için kullanılır. Eğer sunucu ilk anahtarı şifreleme için kullanıyorsa, bu yaklaşım sayesinde istemci her zaman onu çözme için kullanır.
Kullanım şartları şunlardır:
Sunucu ID'si bizim ID'mizden küçükse:
Şifreleme: Birinci Anahtar
Çözme: İkinci Anahtar
Sunucu ID'si bizim ID'mizden büyükse:
Şifreleme: İkinci Anahtar
Çözme: Birinci Anahtar
ID'ler eşitse (neredeyse imkansız):
Şifreleme: Birinci Anahtar
Çözme: Birinci Anahtar
Kanalda İletişim
Tüm sonraki paket alışverişleri kanalda gerçekleşecek ve şifreleme için kanal anahtarları kullanılacaktır.
Yeni oluşturulan bir kanalda aynı dht.getSignedAddressList
isteğini gönderelim, farkı görelim.
Paketi, aynı adnl.packetContents
yapısını kullanarak kanal için oluşturalım:
89cd42d1 -- TL ID adnl.packetContents
0f c1fbe8c4ab8f8e733de83abac17915 -- rand1, 15 (0f) rastgele byte
c4000000 -- flags (0x00c4) -> 0b0000000011000100
-- mesaj (çünkü ikinci bit = 1)
7af98bb4 -- TL ID adnl.message.query
fe3c0f39a89917b7f393533d1d06b605b673ffae8bbfab210150fe9d29083c35 -- query_id
04 ed4879a9 000000 -- sorgu (bizim dht.getSignedAddressList byte olarak paketlenmiş hali, dolgu 3 ile)
0200000000000000 -- seqno (altıncı bitin 1 olması nedeniyle mevcut), 2 çünkü bu bizim ikinci mesajımız
0100000000000000 -- confirm_seqno (yedinci bitin 1 olması nedeniyle mevcut), 1 çünkü bu sunucudan alınan son seqno
07 e4092842a8ae18 -- rand2, 7 (07) rastgele byte
Kanal içindeki paketler oldukça basittir ve esasen dizilerden (seqno) ve mesajlardan oluşur.
Seri hale getirdikten sonra, bir önceki gibi, paketimizin sha256 hash'ini hesaplıyoruz. Ardından paketi, kanalın çıkan paketleri için tasarlanan anahtar kullanarak şifreliyoruz. Hesaplayın
pub.aes
şifreleme anahtarının ID'sini, ve paketimizi oluşturalım:
bcd1cf47b9e657200ba21d94b822052cf553a548f51f539423c8139a83162180 -- giden mesajlarımızın şifreleme anahtarının ID'si
6185385aeee5faae7992eb350f26ba253e8c7c5fa1e3e1879d9a0666b9bd6080 -- sha256 içerik hash'i (şifrelemeden önce)
... -- paketin şifrelenmiş içeriği
Paketimizi UDP ile gönderiyoruz ve yanıt bekliyoruz. Yanıt olarak, gönderdiğimiz ile aynı türde bir paket alacağız (aynı alanlar), ancak dht.getSignedAddressList
isteğimize verilen yanıtı içerir.
Diğer Mesaj Türleri
Temel iletişim için, yukarıda tartıştığımız gibi adnl.message.query
ve adnl.message.answer
gibi mesajlar kullanılır, fakat bazı durumlarda kullanılan diğer mesaj türleri de vardır, bu bölümde bunları tartışacağız.
adnl.message.part
Bu mesaj türü, adnl.message.answer
gibi diğer olası mesaj türlerinden bir parçadır. Bu yöntem, mesajın bir tek UDP datagramında iletilemeyecek kadar büyük olduğu durumlarda kullanılmaktadır.
adnl.message.part
hash:int256 -- orijinal mesajın sha256 hash'i
total_size:int -- orijinal mesaj boyutu
offset:int -- orijinal mesajın başına göre offset
data:bytes -- orijinal mesajın bir veri parçası
= adnl.Message;
Bu nedenle, orijinal mesajı bir araya getirmek için, birkaç parçayı elde etmemiz ve offsetlerine göre bunları tek bir byte dizisinde birleştirmemiz gerekir.
Ve ardından bunu bir mesaj olarak işlemek için (bu byte dizisindeki ID ön eki ile).
adnl.message.custom
adnl.message.custom data:bytes = adnl.Message;
Bu tür mesajlar, daha üst düzeydeki mantığın istek-cevap formatıyla uyumlu olmaması durumunda kullanılır; bu tür mesajlar, yalnızca bir dizi bayt taşıdıkları için işlemi tamamen daha yüksek seviyeye taşımaya olanak tanır; query_id ve diğer alanlar olmadan.
Not: Bu tür mesajlar, örneğin, RLDP'de kullanılır, çünkü çok sayıda isteğe tek bir yanıt olabilir.
Bu mantık RLDP'nin kendisi tarafından kontrol edilir.
Sonuç
İletişim, bu makalede tarif edilen mantık temelinde devam eder, ancak paketlerin içeriği, DHT ve RLDP gibi üst düzey protokollere bağlıdır.
References
Burada orijinal makaleye Oleg Baranov göre bir bağlantı.