এই পর্বে যা শিখবেন
- UDP কী দেয় আর কী দেয় না, আর TCP-র byte stream থেকে datagram-এর সীমানা কেন আলাদা — C-তে চালিয়ে দেখে
- ৮ বাইটের header-এর প্রতিটি ঘর, বাইট ধরে একটি উদাহরণ, আর pseudo-header-সহ checksum হাতে কষে যাচাই
- Datagram-এর সর্বোচ্চ আকার, MTU আর fragmentation-এর সমস্যা, আর DNS-এর ৫১২ থেকে ১,২৩২ বাইটের ইতিহাস
- DNS, DHCP, NTP, VoIP, গেম, QUIC, WireGuard আর multicast কেন UDP বেছে নেয় — আর তখন অ্যাপকে কী কী নিজে করতে হয়
- Connected UDP socket, NAT ও firewall-এর timeout, আর UDP amplification আক্রমণ কীভাবে কাজ করে ও কীভাবে ঠেকানো হয়
১ · UDP IP-র ওপর কী যোগ করে — আর কী করে না
পর্ব ২৭-এ দেখেছেন, transport layer-এর প্রথম কাজ হলো একটি host-এ চলা অনেক প্রোগ্রামের মধ্যে ঠিক প্রোগ্রামটিকে খুঁজে ডেটা দেওয়া। IP packet-কে শুধু কম্পিউটার পর্যন্ত নেয়; তারপর কোন প্রোগ্রাম পাবে, তা ঠিক করে port নম্বর — এর নাম demultiplexing (বিস্তারিত পর্ব ২৭-এ)। UDP (User Datagram Protocol) ঠিক এটুকু করে, প্রায় এর বেশি কিছু নয়।
IP নিজে best effort: packet হারাতে পারে, দুবার আসতে পারে, আগে-পরে আসতে পারে। UDP এর ওপর যোগ করে মাত্র দুটি জিনিস:
- Port নম্বর — উৎস port আর গন্তব্য port, যাতে kernel ঠিক socket-এ datagram দিতে পারে।
- একটি checksum — পথে বাইট বদলে গেলে প্রাপক যাতে ধরতে পারে আর datagram ফেলে দিতে পারে। IPv4-এ এটি ঐচ্ছিক, IPv6-এ বাধ্যতামূলক (অংশ ৪)।
আর যা নেই, তার তালিকাই আসলে UDP-কে বোঝার চাবি:
| বৈশিষ্ট্য | UDP | TCP (পর্ব ২৯–৩২) |
|---|---|---|
| সংযোগ স্থাপন | নেই — connectionless, প্রথম packet-এই ডেটা | three-way handshake, তারপর ডেটা |
| পৌঁছানোর নিশ্চয়তা | নেই; হারালে কেউ জানায় না | ACK আর retransmission দিয়ে নিশ্চিত |
| ক্রম | নেই; যেভাবে পৌঁছায় সেভাবেই | sequence number দিয়ে ঠিক ক্রমে |
| Flow control | নেই; প্রাপকের buffer ভরলে datagram নীরবে বাদ | receive window দিয়ে |
| Congestion control | নেই; অ্যাপকে নিজে ভাবতে হয় | slow start, AIMD, CUBIC, BBR |
| ডেটার চেহারা | আলাদা আলাদা বার্তা — সীমানা অক্ষুণ্ণ | একটানা byte stream |
| Header | ৮ বাইট, স্থির | ২০–৬০ বাইট |
| একসাথে অনেককে (multicast/broadcast) | পারে | পারে না — সংযোগ শুধু দুজনের |
| সার্ভারে অবস্থা | প্রায় শূন্য; এক socket-এ সব ক্লায়েন্ট | প্রতি সংযোগে আলাদা অবস্থা আর socket |
ডাকঘরের দুই রকম সেবা ভাবুন। TCP হলো ট্র্যাকিং নম্বরওয়ালা কুরিয়ার সার্ভিস: পাঠানোর আগে দুই পক্ষের খাতায় নাম ওঠে, প্রতিটি প্যাকেট পৌঁছালে সই নেওয়া হয়, হারালে আবার পাঠানো হয়, আর দশটি প্যাকেট পাঠালে প্রাপক ক্রম মিলিয়ে সাজিয়ে পান। UDP হলো ডাকবাক্সে ফেলা সাধারণ পোস্টকার্ড: খামের ওপর শুধু “কার কাছে, কার থেকে” লেখা, ডাকবাক্সে ফেলেই আপনার কাজ শেষ। পৌঁছাল কি না কেউ জানাবে না, তিনটি কার্ড আগে-পরে পৌঁছাতে পারে। কিন্তু একটি সুবিধা আছে: প্রতিটি কার্ড আলাদা একটি কার্ড হিসেবেই পৌঁছায় — দুটি কার্ড জোড়া লেগে একটি হয়ে যায় না। আর পোস্টকার্ডের জন্য কাউন্টারে লাইন দিতে হয় না — সেজন্যই দ্রুত।
“নিশ্চয়তাহীন” শুনে UDP-কে খারাপ মনে হতে পারে, কিন্তু এটি একটি নকশার সিদ্ধান্ত। নিশ্চয়তার দাম আছে — অপেক্ষা, অতিরিক্ত বার্তা, সার্ভারে প্রতি সংযোগের অবস্থা। ছোট প্রশ্ন-উত্তরের অ্যাপ বা যার কাছে দেরিতে আসা ডেটা অর্থহীন (ভিডিও কলের ২০০ মিলিসেকেন্ড আগের কণ্ঠ), তার জন্য UDP ঠিক যতটুকু দরকার ততটুকু দেয়। আর যার পুরো নিশ্চয়তা দরকার কিন্তু TCP-র আচরণ পছন্দ নয়, সে UDP-র ওপর নিজের নিয়ম বানায় — যেমন QUIC (পর্ব ৩৩)।
UDP = IP + port + checksum. এর বেশি যা কিছু — হারানো ডেটা আবার পাঠানো, ক্রম মেলানো, গতি নিয়ন্ত্রণ — লাগলে তা অ্যাপ্লিকেশনকেই করতে হবে। তাই UDP বেছে নেওয়া মানে দায়িত্বও নেওয়া।
২ · প্রতিটি datagram একটি পূর্ণ বার্তা — TCP-র byte stream থেকে মৌলিক পার্থক্য
ধরুন একটি প্রোগ্রাম পরপর তিনবার পাঠাল: ১ বাইট, ১০ বাইট, ৩০ বাইট। প্রাপক কী পাবে?
- TCP-তে: TCP-র কাছে “বার্তা” বলে কিছু নেই, শুধু বাইটের একটি ধারা। প্রাপকের
recv()একবারে ৪১ বাইট পেতে পারে, বা ৫ আর ৩৬, বা অন্য যেকোনোভাবে ভাগ হয়ে। তাই TCP-র ওপর প্রতিটি protocol-কে নিজের সীমানা-চিহ্ন বানাতে হয় — HTTP-তেContent-Lengthবা ফাঁকা লাইন, অনেক protocol-এ শুরুতে দৈর্ঘ্য লেখা। - UDP-তে: প্রতিটি
sendto()একটি আলাদা datagram, আর প্রতিটিrecvfrom()ঠিক একটি পুরো datagram ফেরত দেয় — ১, ১০, ৩০. এর নাম message boundary রক্ষা। (কোনো datagram হারাতে বা আগে-পরে আসতে পারে, কিন্তু অর্ধেক datagram কখনো আসে না।)
নিচের প্রোগ্রাম একই কম্পিউটারের loopback-এ দুটি UDP socket খোলে — একটি পাঠায়, একটি পায় (প্রাপকের port OS বেছে দেয়)। প্রথমে তিনটি আলাদা আকারের datagram, তারপর দেখে প্রাপকের buffer datagram-এর চেয়ে ছোট হলে কী হয়। এখানে দুটি system call মূল: sendto() আর recvfrom(). UDP socket প্রোগ্রামিং বিস্তারিত পর্ব ৪২-এ।
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(void) {
int rx = socket(AF_INET, SOCK_DGRAM, 0), tx = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in a = {0}; /* receiver: 127.0.0.1, port chosen by OS */
a.sin_family = AF_INET;
a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
socklen_t al = sizeof a;
if (bind(rx, (struct sockaddr *)&a, sizeof a) < 0 || getsockname(rx, (struct sockaddr *)&a, &al) < 0) {
perror("bind"); return 1;
}
char buf[2048], big[100];
memset(big, 'x', sizeof big);
size_t sizes[3] = {1, 10, 30};
for (int i = 0; i < 3; i++) /* three datagrams of different sizes */
printf("sendto #%d: %zd bytes\n", i + 1, sendto(tx, big, sizes[i], 0, (struct sockaddr *)&a, sizeof a));
for (int i = 0; i < 3; i++) /* each recvfrom returns exactly one datagram */
printf("recvfrom #%d: %zd bytes\n", i + 1, recvfrom(rx, buf, sizeof buf, 0, NULL, NULL));
sendto(tx, big, 100, 0, (struct sockaddr *)&a, sizeof a);
sendto(tx, big, 100, 0, (struct sockaddr *)&a, sizeof a);
char small[8];
ssize_t n = recv(rx, small, sizeof small, 0); /* 100-byte datagram, 8-byte buffer */
printf("recv into 8-byte buffer: %zd bytes (rest of that datagram is discarded)\n", n);
n = recv(rx, small, sizeof small, MSG_TRUNC); /* Linux: return the real datagram length */
printf("same again with MSG_TRUNC: returns %zd (only 8 copied)\n", n);
n = recv(rx, small, sizeof small, MSG_DONTWAIT);
printf("queue now: %s\n", n < 0 ? "empty (EAGAIN)" : "not empty");
return 0;
}আউটপুট:
sendto #1: 1 bytes
sendto #2: 10 bytes
sendto #3: 30 bytes
recvfrom #1: 1 bytes
recvfrom #2: 10 bytes
recvfrom #3: 30 bytes
recv into 8-byte buffer: 8 bytes (rest of that datagram is discarded)
same again with MSG_TRUNC: returns 100 (only 8 copied)
queue now: empty (EAGAIN)
সীমানা অক্ষুণ্ণ: তিনটি sendto(), তিনটি recvfrom() — আর প্রতিটি ঠিক ১, ১০, ৩০ বাইট। buf ২,০৪৮ বাইটের হলেও প্রথম recvfrom() পরের datagram-গুলো টেনে আনেনি। TCP socket হলে একই কোডে প্রথম recv() খুব সম্ভব একসাথে ৪১ বাইট পেত।
ছোট buffer-এ কাটা পড়া: ১০০ বাইটের datagram ৮ বাইটের buffer-এ পড়লে প্রোগ্রাম পায় প্রথম ৮ বাইট, আর বাকি ৯২ বাইট চিরতরে হারায় — পরের recv() সেগুলো দেয় না, দেয় পরের datagram. দ্বিতীয় ১০০ বাইটের datagram-এ MSG_TRUNC flag দেওয়ায় Linux আসল দৈর্ঘ্য ১০০ ফেরত দিল, যদিও কপি করল ৮ বাইটই — এভাবে প্রোগ্রাম বুঝতে পারে ডেটা কাটা পড়েছে। শেষ লাইনে queue ফাঁকা: দুটি datagram-ই পড়া শেষ। এই আচরণ OS ভেদে আলাদা: recv()-এ MSG_TRUNC দিয়ে আসল দৈর্ঘ্য পাওয়া Linux-এর বৈশিষ্ট্য; বহনযোগ্য পথ হলো recvmsg()-এর msg_flags-এ MSG_TRUNC আছে কি না দেখা; আর Windows-এ এমন অবস্থায় recvfrom() ত্রুটি WSAEMSGSIZE দেয়। শিক্ষা: UDP-তে buffer সবসময় প্রত্যাশিত সবচেয়ে বড় datagram-এর সমান রাখুন।
দুটি প্রচলিত ভুল: UDP-তে “বাকিটা পরের recv-এ পড়ব” ভেবে লুপ লেখা (যা কাটা পড়ল তা গেল), আর TCP-তে “একটি send = একটি recv” ধরে নেওয়া — loopback-এ ঠিক দেখালেও বাস্তব নেটওয়ার্কে একদিন ভাঙবেই। আরেকটি সূক্ষ্ম পার্থক্য: TCP-তে recv() 0 ফেরত দিলে মানে অপর প্রান্ত সংযোগ বন্ধ করেছে; UDP-তে 0 মানে শুধু একটি খালি (০ বাইটের) datagram এসেছে — সেটি সম্পূর্ণ বৈধ।
৩ · ৮ বাইটের header — ঘর ধরে ধরে, বাইট ধরে ধরে
UDP-র নির্দিষ্টকরণ RFC 768, প্রকাশ আগস্ট ১৯৮০, লেখক Jon Postel — পুরো দলিলটি মাত্র তিন পাতার মতো, ইন্টারনেটের সবচেয়ে ছোট মূল মানগুলোর একটি। এর পেছনের গল্পটা আরও পুরোনো: ১৯৭০-এর দশকের শুরুতে TCP ছিল একটিই বড় protocol, যা ঠিকানা আর নির্ভরযোগ্যতা দুটোই সামলাত। নেটওয়ার্কে কণ্ঠ পাঠানোর পরীক্ষা করা গবেষকেরা দেখান যে কণ্ঠের জন্য হারানো packet আবার পাঠানোর অপেক্ষা ক্ষতিকর। এই ধরনের যুক্তিতে ১৯৭৮ সালের দিকে TCP ভেঙে আলাদা IP আর TCP হয় — আর “নির্ভরযোগ্যতা ছাড়া শুধু port” দেওয়ার জন্য পরে আসে UDP.
RFC-র ছবিটি এরকম — প্রতিটি সারি ৩২ bit (৪ বাইট), প্রতিটি ঘর ১৬ bit (২ বাইট):
0 7 8 15 16 23 24 31 (bit)
┌─────────────────┬─────────────────┐
│ Source Port │ Destination Port│ byte 0-3
├─────────────────┼─────────────────┤
│ Length │ Checksum │ byte 4-7
├─────────────────┴─────────────────┤
│ data (payload) ... │ byte 8 ...
└───────────────────────────────────┘
| ঘর | আকার | মানে | খেয়াল রাখার কথা |
|---|---|---|---|
| Source Port | ১৬ bit | প্রেরকের port — উত্তর এখানেই আসবে | RFC 768 অনুযায়ী ঐচ্ছিক: উত্তর না লাগলে 0 রাখা যায়; সাধারণত OS একটি ephemeral port বসায় |
| Destination Port | ১৬ bit | প্রাপকের port — kernel এটি দেখে socket খোঁজে | পরিচিত সেবার নির্দিষ্ট নম্বর: DNS 53, NTP 123 |
| Length | ১৬ bit | header + ডেটা, বাইটে | সর্বনিম্ন ৮ (ডেটাহীন datagram বৈধ); IPv4-এ IP header থেকেই হিসাব করা যায়, তাই কিছুটা অতিরিক্ত তথ্য |
| Checksum | ১৬ bit | pseudo-header + header + ডেটার one's complement যোগফলের উল্টো | IPv4-এ 0 = “হিসাব করা হয়নি”; IPv6-এ বাধ্যতামূলক |
সব ঘর network byte order-এ (বড় বাইট আগে)। এবার একটি বাস্তব উদাহরণ ধরে header ভরে দেখি: 192.168.1.10-এর একটি প্রোগ্রাম 198.51.100.53-এর port 53-এ ৫ বাইটের লেখা Hello পাঠাচ্ছে (ঠিকানাটি ডকুমেন্টেশনের জন্য সংরক্ষিত ব্লক থেকে; সত্যিকারের DNS সার্ভার এটিকে বৈধ প্রশ্ন মানবে না, কিন্তু header-এর হিসাব শেখার জন্য ছোট ডেটাই ভালো)।
- অ্যাপ
sendto()"Hello" ৫ বাইট - উৎস port
50000OS বেছে দিল - গন্তব্য port
53অ্যাপ বলেছে - Length
8 + 5 = 13বাইট - Checksum
0x2c61IP-র হাতে
| Source Port (বাইট ০–১) | |
| Destination Port (বাইট ২–৩) | |
| Length (বাইট ৪–৫) | |
| Checksum (বাইট ৬–৭) | |
| IP-র জন্য |
- অ্যাপ শুধু ডেটা আর গন্তব্য দিল।
sendto(fd, "Hello", 5, 0, &dst, ...)— গন্তব্য198.51.100.53:53. Header বানানোর দায় kernel-এর। - উৎস port: 50000 = 0xc350. Socket আগে কোনো port-এ bind না হলে প্রথম
sendto()-এর সময় kernel একটি খালি ephemeral port বেছে দেয় — এখানে ধরি 50000. Hex-এ 0xc350, তাই বাইট দুটিc3 50. - গন্তব্য port: 53 = 0x0035. বাইট দুটি
00 35. প্রাপকের kernel এই সংখ্যা দেখেই ঠিক করবে কোন socket পাবে। - Length: ৮ + ৫ = ১৩ = 0x000d. Header-এর ৮ বাইট আর ডেটার ৫ বাইট। Checksum ঘর আপাতত শূন্য রাখা হলো — নিজের হিসাবে নিজেকে যোগ না করার জন্য।
- Checksum: 0x2c61, তারপর IP-র হাতে। Pseudo-header + header + ডেটা যোগ করে উল্টে পাওয়া গেল 0x2c61 (পরের অংশে হাতে কষব)। পুরো datagram তারে:
c3 50 00 35 00 0d 2c 61 48 65 6c 6c 6f— ১৩ বাইট। IP এর সামনে ২০ বাইটের header বসায়, protocol ঘরে 17 (UDP-র protocol number), মোট দৈর্ঘ্য ৩৩.
এই ছোট্ট header-এ TCP-র sequence number, ACK, window, flag — কিছুই নেই, আর কাউকে কোনো “সংযোগের অবস্থা” মনে রাখতে হয় না। তাই একটি DNS সার্ভার একটি মাত্র socket দিয়ে লাখো ক্লায়েন্টের প্রশ্ন সামলাতে পারে।
৪ · Checksum হাতে কষা — pseudo-header থেকে প্রাপকের যাচাই পর্যন্ত
Internet checksum-এর পদ্ধতি পর্ব ০৯-এ (error detection) দেখেছেন: সব ডেটাকে ১৬ bit-এর শব্দে ভাগ করে one's complement যোগ (১৬ bit ছাড়িয়ে যাওয়া carry আবার নিচে যোগ — end-around carry), শেষে সব bit উল্টে দেওয়া। UDP-র বিশেষত্ব হলো কী কী যোগ হবে:
- Pseudo-header (IPv4-এ ১২ বাইট): উৎস IP (৪), গন্তব্য IP (৪), একটি শূন্য বাইট আর protocol 17 (২), UDP length (২)। এটি তারে পাঠানো হয় না — শুধু হিসাবে যোগ হয়।
- UDP header — checksum ঘর শূন্য ধরে।
- ডেটা — বাইটের সংখ্যা বিজোড় হলে শেষে একটি শূন্য বাইট জুড়ে (এটিও তারে যায় না)।
Pseudo-header কেন? যাতে ভুল ঠিকানায় পৌঁছানো datagram-ও ধরা পড়ে: পথে IP header-এর গন্তব্য ঠিকানা নষ্ট হয়ে অন্য কম্পিউটারে গেলে সেখানে pseudo-header আলাদা হবে, checksum মিলবে না। এর একটি পরিণাম পর্ব ১৯-এ (NAT) দেখেছেন: NAT শুধু IP ঠিকানা বদলালেও UDP checksum বদলাতে হয়, আর সেখানে incremental হালনাগাদের সূত্রও আছে। এখানে আমরা পুরো হিসাবটা একবার হাতে করি।
- Pseudo-header
শব্দ ১–৬IP-র ঘর - UDP header
শব্দ ৭–১০checksum = 0 - ডেটা
শব্দ ১১–১৩শেষে 00 জোড়া - উল্টানো
~d39e= 2c61 - প্রাপক
সব + checksumffff?
| এই ধাপে যোগ হলো | |
| চলমান যোগফল | |
| Carry ঘুরে এল | |
| ফল |
- Pseudo-header-এর ৬টি শব্দ। 192.168 =
c0a8, 1.10 =010a, 198.51 =c633, 100.53 =6435, তারপর শূন্য বাইট + protocol 17 =0011, UDP length 13 =000d. যোগের পথে c1b2 + c633 = 0x187e5 — ১৬ bit পেরিয়েছে, তাই উপচে পড়া ১ নিচে যোগ: 87e5 + 1 = 87e6. শেষে যোগফলec39. - UDP header-এর ৪টি শব্দ। উৎস port
c350, গন্তব্য0035, length000d, checksum0000. ec39 + c350 = 0x1af89 → af89 + 1 = af8a; তারপর + 0035 + 000d + 0000 =afcc. লক্ষ করুন, length দুবার যোগ হলো — একবার pseudo-header-এ, একবার header-এ। এটাই নিয়ম। - ডেটার ৩টি শব্দ। “Hello” = 48 65 6c 6c 6f, বিজোড় বলে শেষে 00 জুড়ে
4865 6c6c 6f00. afcc + 4865 = f831; + 6c6c = 0x1649d → 649d + 1 = 649e; + 6f00 =d39e. - উল্টানো: ~d39e = 2c61. প্রতিটি bit উল্টে (প্রতিটি hex অঙ্ক f থেকে বিয়োগ করে) পাওয়া গেল
2c61— এটিই checksum ঘরে বসবে। হিসাবের ফল 0000 হলে পাঠানো হতোffff, কারণ 0 মানে “checksum নেই” (দুটিই one's complement-এ “শূন্য”). - প্রাপক সব কিছু checksum-সহ যোগ করে। নিজে pseudo-header বানিয়ে (নিজের দেখা IP ঠিকানা থেকে) সব ১৩টি শব্দ আর পাওয়া checksum যোগ করল: d39e + 2c61 =
ffff. সব bit ১ মানে কোনো ত্রুটি ধরা পড়েনি — datagram socket-এ যাবে। - একটি bit নষ্ট হলে। ধরুন পথে “Hello”-এর
e(0x65) একটি bit উল্টেa(0x61) হলো — শব্দ 4865 হলো 4861, যোগফল ৪ কম:fffb. ffff নয়, তাই kernel datagram নীরবে ফেলে দেয় (ত্রুটির পরিসংখ্যানে একটি সংখ্যা বাড়ে মাত্র) — প্রেরক কিছুই জানে না।
একই হিসাব কোডে, প্রতিটি শব্দ আর চলমান যোগফল ছেপে — যাতে stepper-এর প্রতিটি সংখ্যা মিলিয়ে নিতে পারেন। শেষে প্রাপকের যাচাই আর একটি নষ্ট bit ধরা পড়া:
// UDP checksum over IPv4 pseudo-header + UDP header + data (RFC 768 / RFC 1071).
const hex = n => n.toString(16).padStart(4, "0");
const ipW = s => { const o = s.split(".").map(Number); return [o[0] << 8 | o[1], o[2] << 8 | o[3]]; };
function words(src, dst, sport, dport, data, csum) {
const len = 8 + data.length;
const b = [...data, ...(data.length % 2 ? [0] : [])]; // pad odd length with a zero byte
const d = []; for (let i = 0; i < b.length; i += 2) d.push(b[i] << 8 | b[i + 1]);
return { len, w: [...ipW(src), ...ipW(dst), 17, len, sport, dport, len, csum, ...d] };
}
function sum(w, show) {
let s = 0;
w.forEach((x, i) => {
s += x; let note = "";
if (s > 0xffff) { s = (s & 0xffff) + 1; note = " carry -> wrap"; } // end-around carry
if (show) console.log(String(i + 1).padStart(2), hex(x), " sum", hex(s) + note);
});
return s;
}
const SRC = "192.168.1.10", DST = "198.51.100.53", data = [..."Hello"].map(c => c.charCodeAt(0));
const tx = words(SRC, DST, 50000, 53, data, 0); // checksum field = 0 while computing
console.log("words 1-6 pseudo-header, 7-10 UDP header, 11-13 data (UDP length " + tx.len + ")");
const s = sum(tx.w, true);
let ck = ~s & 0xffff; if (ck === 0) ck = 0xffff; // 0 is reserved for "no checksum"
console.log("one's complement of", hex(s), "= checksum", hex(ck));
const rx = words(SRC, DST, 50000, 53, data, ck);
console.log("receiver: sum incl. checksum =", hex(sum(rx.w, false)), "-> ffff means OK");
const bad = data.slice(); bad[1] ^= 0x04; // one bit flips: 'e' -> 'a'
const rx2 = words(SRC, DST, 50000, 53, bad, ck);
console.log("corrupted 'Hallo': sum =", hex(sum(rx2.w, false)), "-> not ffff, datagram dropped");আউটপুট:
words 1-6 pseudo-header, 7-10 UDP header, 11-13 data (UDP length 13)
1 c0a8 sum c0a8
2 010a sum c1b2
3 c633 sum 87e6 carry -> wrap
4 6435 sum ec1b
5 0011 sum ec2c
6 000d sum ec39
7 c350 sum af8a carry -> wrap
8 0035 sum afbf
9 000d sum afcc
10 0000 sum afcc
11 4865 sum f831
12 6c6c sum 649e carry -> wrap
13 6f00 sum d39e
one's complement of d39e = checksum 2c61
receiver: sum incl. checksum = ffff -> ffff means OK
corrupted 'Hallo': sum = fffb -> not ffff, datagram dropped
১৩টি শব্দের তিনটিতে (৩, ৭, ১২) যোগফল ১৬ bit ছাড়িয়েছে — carry -> wrap লাইনগুলো সেই end-around carry. শব্দ ১০ হলো checksum ঘর নিজে, হিসাবের সময় 0000. চূড়ান্ত যোগফল d39e, উল্টে 2c61 — stepper-এর সাথে হুবহু মিল। প্রাপকের দিকে checksum-সহ যোগফল ffff, আর একটি bit উল্টানো “Hallo”-তে fffb. কোডে if (ck === 0) ck = 0xffff লাইনটি RFC 768-এর নিয়ম: হিসাবের ফল শূন্য হলে সব-১ পাঠাতে হয়। নিজে চেষ্টা করুন: "Hello" বদলে "Hi" করে দেখুন — length 10 হবে, padding লাগবে না।
Checksum 0, IPv6, আর checksum-এর সীমা
- IPv4-এ 0 মানে “নেই”: প্রেরক checksum হিসাব না করলে ঘরে 0 বসায়, প্রাপক তখন যাচাই করে না। আজ প্রায় সব সিস্টেম checksum চালু রাখে, আর প্রায়ই network card নিজেই হিসাব করে (checksum offload) — তাই প্রেরকের নিজের যন্ত্রে ধরা packet-এ checksum ভুল দেখালে ঘাবড়াবেন না।
- IPv6-এ বাধ্যতামূলক: IPv6 header-এ নিজের কোনো checksum নেই (router-দের কাজ কমাতে বাদ দেওয়া হয়েছে), তাই ঠিকানা নষ্ট হওয়া ধরার শেষ সুযোগ transport-এর checksum. এজন্য IPv6-এ UDP checksum 0 রাখা নিষেধ — পর্ব ২১-এ (IPv6) header-এর আলোচনায় এর কারণ দেখেছেন। IPv6 pseudo-header-এ ১২৮ bit-এর দুটি ঠিকানা, ৩২ bit-এর দৈর্ঘ্য আর next header 17 থাকে।
- দুর্বলতা: ১৬ bit-এর এই যোগফল দুটি শব্দের অদলবদল ধরতে পারে না (যোগ ক্রমনিরপেক্ষ), আর কয়েকটি ভুল একে অপরকে কাটাকাটি করলেও ধরা পড়ে না। Ethernet আর Wi-Fi-র CRC (পর্ব ০৯) বেশিরভাগ ত্রুটি আগেই ধরে; checksum শেষ স্তরের সস্তা পাহারা, নিরাপত্তা নয় — ইচ্ছাকৃত পরিবর্তন ঠেকাতে লাগে এনক্রিপশন বা cryptographic MAC.
৫ · কত বড় datagram — ৬৫,৫০৭ বাইটের তত্ত্ব আর ১,২৩২ বাইটের বাস্তব
Length ঘর ১৬ bit, তাই UDP datagram সর্বোচ্চ ৬৫,৫৩৫ বাইট হতে পারত। কিন্তু datagram-কে একটি IPv4 packet-এর ভেতরে আঁটতে হয়, আর IPv4-এর Total Length-ও ১৬ bit — পুরো packet ৬৫,৫৩৫ বাইটের বেশি নয়। তাই:
IP option থাকলে আরও কম; IPv6-এ ৬৫,৫৩৫ − ৮ = ৬৫,৫২৭ (Payload Length-এ IPv6-এর ৪০ বাইট ধরা হয় না), jumbogram ছাড়া
এটা তাত্ত্বিক সীমা। এত বড় datagram পাঠালে IP তাকে ভেঙে টুকরো করবে, কারণ সাধারণ Ethernet-এর MTU ১,৫০০ বাইট — এটাই fragmentation (পর্ব ১৬)। UDP-র জন্য fragmentation বিশেষ খারাপ:
- একটি টুকরো হারালে পুরো datagram যায়। UDP-তে আবার পাঠানোর ব্যবস্থা নেই, আর প্রাপক কিছু টুকরো জমিয়ে অপেক্ষা করে শেষে সব ফেলে দেয়। টুকরো যত বেশি, পৌঁছানোর সম্ভাবনা তত কম (অনুশীলন ১-এ হিসাব)।
- পরের টুকরোয় port থাকে না। UDP header থাকে শুধু প্রথম টুকরোয়, তাই firewall আর NAT বাকি টুকরো চিনতে পারে না — অনেক নেটওয়ার্ক টুকরো সরাসরি ফেলে দেয়।
- IPv6-এ পথের router টুকরো করে না। বড় packet ফেলে দিয়ে ICMPv6 “Packet Too Big” পাঠায়, আর অনেক firewall সেই ICMP আটকায় — ফলে datagram নিঃশব্দে হারায়।
| সংখ্যা | কোথা থেকে | কোথায় দেখবেন |
|---|---|---|
| ১,৪৭২ বাইট | ১,৫০০ − ২০ − ৮ (IPv4, Ethernet) | fragmentation ছাড়া IPv4 Ethernet-এ সবচেয়ে বড় payload; IPv6-এ ১,৫০০ − ৪০ − ৮ = ১,৪৫২ |
| ১,২৩২ বাইট | ১,২৮০ − ৪০ − ৮ (IPv6-এর সর্বনিম্ন MTU) | যেকোনো IPv6 পথে নিশ্চিত আঁটে; DNS-এর এখনকার প্রচলিত পরামর্শ |
| ১,২০০ বাইট | QUIC-এর নিয়ম (RFC 9000) | QUIC-এর প্রথম (Initial) packet বহনকারী datagram অন্তত এত বড় করতে হয়, যাতে পথ এটুকু বহন করতে পারে কি না শুরুতেই বোঝা যায় |
| ৫১২ বাইট | RFC 1035 (১৯৮৭) | পুরোনো DNS-এর UDP উত্তরের সীমা |
| ১,৪৬৪ বাইট | ১,৪৯২ − ২০ − ৮ | PPPoE ব্রডব্যান্ডে (MTU ১,৪৯২) — বাংলাদেশসহ অনেক দেশের বহু ISP এই ধরনের সংযোগ ব্যবহার করে |
DNS-এর গল্প: ৫১২ → EDNS → ১,২৩২
১৯৮৭ সালের DNS মান (RFC 1035) UDP-তে উত্তর সর্বোচ্চ ৫১২ বাইটে সীমিত রাখে। বড় উত্তর হলে সার্ভার ছোট উত্তরে TC (truncated) bit বসায়, আর প্রশ্নকারী একই প্রশ্ন TCP-তে আবার করে। DNSSEC-এর স্বাক্ষর আর IPv6 ঠিকানায় উত্তর বড় হতে থাকায় এল EDNS(0) (প্রথমে RFC 2671, ১৯৯৯; এখন RFC 6891): প্রশ্নকারী জানায় সে কত বাইট পর্যন্ত UDP উত্তর নিতে পারে — প্রায়ই ৪,০৯৬. কিন্তু এত বড় উত্তর মানে fragmentation, আর হারানো বা firewall-এ আটকানো টুকরো বহু রহস্যময় DNS ব্যর্থতার কারণ হয়; টুকরো জাল করে উত্তর বিষাক্ত করার গবেষণাও প্রকাশিত হয়। তাই বড় DNS অপারেটর আর সফটওয়্যার নির্মাতারা “DNS Flag Day 2020” নামের সমন্বিত উদ্যোগে ডিফল্ট EDNS buffer ১,২৩২ বাইট করার পরামর্শ দেন — বড় উত্তর তখন TCP-তে যায়। (সঠিক ডিফল্ট সফটওয়্যার ভেদে ভিন্ন হতে পারে।)
নিজের protocol বানালে datagram ১,২০০–১,৪৭২ বাইটের মধ্যে রাখুন; পথ অজানা হলে ১,২০০-র কাছাকাছি নিরাপদ। বড় ডেটা হলে অ্যাপ নিজেই ছোট ছোট datagram-এ ভাগ করে, প্রতিটিতে নিজের ক্রমিক নম্বর রাখে। পথের MTU খুঁজতে datagram-ভিত্তিক protocol-দের জন্য PLPMTUD নামে পদ্ধতিও আছে (RFC 8899) — ছোট থেকে শুরু করে বড় packet পরীক্ষা করা, ICMP-র ওপর ভরসা না করে।
৬ · কারা UDP বেছে নেয়, আর কেন
UDP বেছে নেওয়ার কারণ সাধারণত চারটির একটি: (ক) ছোট প্রশ্ন-উত্তর, যেখানে handshake-এর খরচ উত্তরের চেয়েও বেশি; (খ) সময়-সংবেদনশীল ডেটা, যেখানে দেরিতে আসা ডেটা অর্থহীন; (গ) একসাথে অনেককে পাঠানো; আর (ঘ) UDP-র ওপর নিজের transport বানানো।
| Protocol / ব্যবহার | প্রচলিত port | কেন UDP |
|---|---|---|
| DNS | 53 (TCP-ও) | একটি প্রশ্ন, একটি উত্তর — দুটি packet-এ কাজ শেষ; হারালে আবার জিজ্ঞেস করা সহজ (পর্ব ৩৫) |
| DHCP | 67/68 | নতুন যন্ত্রের তখনো IP ঠিকানাই নেই, তাই broadcast লাগে — TCP-তে তা অসম্ভব (পর্ব ১৮) |
| NTP | 123 | সময় মাপতে প্রতিটি packet কখন গেল-এল তা জরুরি; TCP-র retransmission আর buffering মাপ ঘোলা করত |
| SNMP | 161/162 | হাজারো যন্ত্রকে নিয়মিত ছোট প্রশ্ন; সমস্যার সময় নেটওয়ার্ক অর্ধেক ভাঙা থাকলেও হালকা protocol কাজ করে |
| Syslog | 514 | যন্ত্র log পাঠিয়েই খালাস; তবে হারানোর ঝুঁকি আছে, তাই গুরুত্বপূর্ণ log-এ এখন TCP বা TLS-ও ব্যবহার হয় |
| VoIP / RTP, ভিডিও কল | সাধারণত পরিবর্তনশীল | ২০ মিলিসেকেন্ডের কণ্ঠের টুকরো দেরিতে এলে বাজানোর সময় পেরিয়ে যায়; আবার পাঠানোর চেয়ে বাদ দেওয়াই ভালো (পর্ব ৪০) |
| অনলাইন গেম | গেম ভেদে | প্রতি মুহূর্তের অবস্থা (কে কোথায়) পাঠানো হয়; পুরোনো অবস্থা হারালে পরেরটাই যথেষ্ট |
| QUIC / HTTP/3 | 443 | নিজের নির্ভরযোগ্যতা আর congestion control নিয়ে UDP-র ওপর নতুন transport, যাতে NAT আর middlebox পেরোতে পারে (পর্ব ৩৩, ৩৭) |
| VPN: WireGuard, OpenVPN, IPsec NAT-T | 51820 (WireGuard-এর প্রচলিত), 1194, 4500 | ভেতরের TCP নিজেই নির্ভরযোগ্যতা দেয়; বাইরে আরেকটি TCP দিলে দুই স্তরের retransmission একে অপরের সাথে লড়ে (“TCP-over-TCP” সমস্যা) — পর্ব ৪৯ |
| IPTV, শেয়ারবাজারের দর, ভিডিও সম্প্রচার | multicast group | একই ডেটা একসাথে হাজারো প্রাপকের কাছে — নিচে দেখুন |
একটি সাধারণ ভুল ধারণা: “YouTube-এর ভিডিও UDP-তে আসে।” রেকর্ড করা ভিডিও সাধারণত HTTP-র ওপর আসে — TCP-তে, বা HTTP/3 হলে QUIC-এর মাধ্যমে; কয়েক সেকেন্ডের buffer থাকায় নির্ভরযোগ্যতাই জরুরি। সরাসরি UDP বেশি দেখা যায় ভিডিও কল, খুব কম delay-এর লাইভ ভিডিও আর ISP-র IPTV-তে।
Multicast: TCP যেখানে পারেই না
TCP-র প্রতিটি সংযোগ ঠিক দুই প্রান্তের: handshake, প্রতিটি প্রাপকের আলাদা ACK, আলাদা window. এক হাজার প্রাপকের প্রত্যেকে ACK পাঠালে প্রেরক ডুবে যাবে (“ACK implosion”), আর কার জন্য কোনটি আবার পাঠাতে হবে তার হিসাব অসম্ভব। UDP-তে এমন কোনো অবস্থা নেই, তাই প্রেরক একটি datagram একটি multicast group ঠিকানায় (যেমন 239.1.1.1) পাঠায়, আর নেটওয়ার্ক যেখানে প্রয়োজন সেখানে কপি করে প্রতিটি সদস্যের কাছে পৌঁছায় (পর্ব ২৬, multicast আর QoS). একই কারণে LAN-এ broadcast (DHCP-র প্রথম প্রশ্ন) আর mDNS-এর মতো স্থানীয় খোঁজাখুঁজিও UDP-তে।
৭ · নিশ্চয়তা লাগলে অ্যাপ নিজে বানায় — timeout, retry, ক্রম আর congestion
UDP যা দেয় না, অ্যাপ দরকারমতো তার ছোট ছোট টুকরো নিজে বানায় — পুরো TCP নয়, শুধু যতটুকু তার কাজে লাগে। সবচেয়ে সাধারণ চারটি কৌশল:
Timeout আর retry
অনুরোধ পাঠিয়ে একটি timer চালু; সময়ের মধ্যে উত্তর না এলে আবার পাঠানো (retransmission), প্রতিবার অপেক্ষা বাড়িয়ে (exponential backoff). DNS এভাবে চলে।
DNS, DHCP, SNMPনিজের ID আর ক্রমিক নম্বর
DNS-এর ১৬ bit-এর query ID উত্তরকে প্রশ্নের সাথে মেলায়, পুরোনো বা নকল উত্তর বাদ দেয়। RTP-র sequence number দিয়ে প্রাপক হারানো আর আগে-পরে আসা packet চেনে।
DNS ID, RTP seqJitter buffer
Packet আসার সময়ের ওঠানামা (jitter) লুকাতে প্রাপক কয়েক দশ মিলিসেকেন্ড জমিয়ে সমান তালে বাজায় (jitter buffer). বেশি দেরিতে আসা packet বাদ — আবার চাওয়া হয় না।
VoIP, ভিডিও কলনিজের congestion control
বেশি ডেটা পাঠালে অ্যাপকে নেটওয়ার্কের ভিড় বুঝে গতি কমাতে হয় — নইলে নিজের আর অন্যদের সব ট্রাফিক ক্ষতিগ্রস্ত। QUIC TCP-র মতো পূর্ণ congestion control রাখে; ভিডিও কলের অ্যাপগুলো delay দেখে bitrate কমায়।
QUIC, WebRTCএখন একটি DNS-এর মতো ক্লায়েন্টের retry ধাপে ধাপে দেখি। প্রথম চেষ্টায় প্রশ্নটিই হারায়, দ্বিতীয় চেষ্টায় সার্ভার উত্তর দেয় কিন্তু উত্তরটি হারায় — ক্লায়েন্টের চোখে দুটি ঘটনা একই রকম দেখায়।
- ক্লায়েন্ট
id=0x1a2btimer চালু - নেটওয়ার্ক
loss ৪০%প্রতি দিকে - DNS সার্ভার
port 53অবস্থা রাখে না - উত্তর
id মিললt = 3.08 s
| সময় | |
| চেষ্টা │ timer | |
| পথে কী হলো | |
| অ্যাপ কী জানে |
- প্রথম চেষ্টা, প্রশ্ন হারাল। ক্লায়েন্ট ID
0x1a2bদিয়ে প্রশ্ন পাঠাল আর ১ সেকেন্ডের timer চালু করল। পথে কোনো ভিড়ের router-এর queue-তে প্রশ্নটি ফেলে দেওয়া হলো। UDP কাউকে কিছু জানায় না। - Timer ফুরাল, দ্বিতীয় চেষ্টা। ১ সেকেন্ডে কিছুই আসেনি। ক্লায়েন্ট একই প্রশ্ন আবার পাঠাল, আর এবার অপেক্ষা দ্বিগুণ — ২ সেকেন্ড। কেন দ্বিগুণ? Loss যদি ভিড়ের কারণে হয়, তাড়াহুড়ো করে আবার পাঠালে ভিড় আরও বাড়বে।
- সার্ভার উত্তর দিল — উত্তর হারাল। প্রশ্নটি এবার পৌঁছাল, সার্ভার উত্তর পাঠাল, কিন্তু ফেরার পথে উত্তর হারাল। সার্ভার জানে না (তার কাছে কাজ শেষ), ক্লায়েন্টও জানে না (তার কাছে আগের মতোই নীরবতা)।
- তৃতীয় চেষ্টা — একই প্রশ্ন আবার। ২ সেকেন্ড পর t = 3.00-এ আবার পাঠানো, timer ৪ সেকেন্ড। সার্ভার একই প্রশ্নের উত্তর দ্বিতীয়বার দিল — এতে কোনো ক্ষতি নেই, কারণ নাম জানতে চাওয়া idempotent: যতবারই জিজ্ঞেস করুন, ফল এক।
- উত্তর পৌঁছাল, ID মিলল। t = 3.08-এ (৮০ মিলিসেকেন্ডের RTT পরে) উত্তর এল, ID
0x1a2bপ্রশ্নের সাথে মিলল, timer বন্ধ, অ্যাপ উত্তর পেল। দ্বিতীয় চেষ্টার উত্তর পরে দেরিতে এসে পৌঁছালেও ক্ষতি নেই: retry-তে ID একই থাকে, আর প্রশ্নটির উত্তর আগেই পাওয়া গেছে, তাই ক্লায়েন্ট বাড়তি কপিটি উপেক্ষা করে। মোট ৩.০৮ সেকেন্ড — অথচ নেটওয়ার্ক ভালো থাকলে লাগত ০.০৮.
নিচের simulator ঠিক এই ক্লায়েন্ট: ৪০% loss প্রতি দিকে, প্রথম timeout ১ সেকেন্ড, প্রতিবার দ্বিগুণ। একটি নির্দিষ্ট seed থেকে “এলোমেলো” সংখ্যা আসে, তাই প্রতিবার চালালে একই ফল — stepper-এর প্রথম প্রশ্নটি এখান থেকেই নেওয়া।
// A DNS-like client over a lossy UDP channel: timeout, retry, exponential backoff.
let seed = 15; // fixed seed: same "random" losses every run
const rand = () => (seed = seed * 16807 % 2147483647) / 2147483647; // Park-Miller
const LOSS = 0.4, RTT = 0.08; // 40% loss each way, 80 ms round trip
function query(name, id, maxTries) {
let t = 0, timeout = 1.0; // first wait 1 s, doubled after every miss
for (let attempt = 1; attempt <= maxTries; attempt++) {
const reqLost = rand() < LOSS, repLost = !reqLost && rand() < LOSS;
const what = reqLost ? "query lost" : repLost ? "reply lost" : "reply id=0x" + id.toString(16);
console.log(`t=${t.toFixed(2)}s try ${attempt}: send id=0x${id.toString(16)}, wait ${timeout}s -> ${what}`);
if (!reqLost && !repLost) {
console.log(` answer for ${name} after ${(t + RTT).toFixed(2)}s (${attempt} ${attempt > 1 ? "tries" : "try"})`);
return true;
}
t += timeout; // nothing came back before the timer fired
timeout *= 2; // exponential backoff: 1, 2, 4, 8 ...
}
console.log(` gave up on ${name} at t=${t.toFixed(2)}s -> SERVFAIL to the app`);
return false;
}
query("abcltech.com", 0x1a2b, 4);
query("example.org", 0x1a2c, 4);
query("example.net", 0x1a2d, 3);আউটপুট:
t=0.00s try 1: send id=0x1a2b, wait 1s -> query lost
t=1.00s try 2: send id=0x1a2b, wait 2s -> reply lost
t=3.00s try 3: send id=0x1a2b, wait 4s -> reply id=0x1a2b
answer for abcltech.com after 3.08s (3 tries)
t=0.00s try 1: send id=0x1a2c, wait 1s -> query lost
t=1.00s try 2: send id=0x1a2c, wait 2s -> reply lost
t=3.00s try 3: send id=0x1a2c, wait 4s -> query lost
t=7.00s try 4: send id=0x1a2c, wait 8s -> query lost
gave up on example.org at t=15.00s -> SERVFAIL to the app
t=0.00s try 1: send id=0x1a2d, wait 1s -> reply id=0x1a2d
answer for example.net after 0.08s (1 try)
প্রথম প্রশ্ন stepper-এর মতো: প্রশ্ন হারাল, তারপর উত্তর হারাল, তৃতীয় চেষ্টায় সফল — ৩.০৮ সেকেন্ডে। দ্বিতীয় প্রশ্ন চারবারেই ব্যর্থ। অপেক্ষার মোট সময় ১ + ২ + ৪ + ৮ = ১৫ সেকেন্ড; তারপর ক্লায়েন্ট হাল ছেড়ে অ্যাপকে ত্রুটি দেয় (বাস্তবের resolver যেমন SERVFAIL বা “timed out” জানায়)। তৃতীয় প্রশ্ন প্রথমবারেই সফল, ০.০৮ সেকেন্ডে। লক্ষ করুন, ৪০% loss-এ প্রতিটি চেষ্টা সফল হওয়ার সম্ভাবনা ০.৬ × ০.৬ = ০.৩৬, কারণ প্রশ্ন আর উত্তর দুটিকেই পৌঁছাতে হবে। বাস্তবে Linux-এর glibc resolver-এ সময় আর চেষ্টার সংখ্যা ঠিক করে /etc/resolv.conf-এর timeout: আর attempts: option. নিজে চেষ্টা করুন: LOSS = 0.4 বদলে 0 করুন — অনুশীলন ২ দেখুন।
UDP অ্যাপের শিষ্টাচার — RFC 8085
TCP-র congestion control সবার জন্য একটি “ভদ্রতার চুক্তি”: ভিড় দেখলে সবাই গতি কমায়। কোনো UDP অ্যাপ loss দেখেও একই জোরে পাঠালে সে TCP-র ট্রাফিককে ঠেলে সরায়, আর চরম অবস্থায় নেটওয়ার্ক “congestion collapse”-এ পড়ে — link ভরা, কিন্তু কাজের ডেটা প্রায় পৌঁছায় না। তাই IETF-এর UDP নির্দেশিকা (RFC 8085, ২০১৭, BCP 145) মোটা দাগে বলে: বেশি ডেটা পাঠালে TCP-র সাথে তুলনীয় congestion control রাখা; অল্প ডেটার অ্যাপ প্রতি RTT-তে মোটামুটি একটির বেশি অনুরোধ না পাঠানো আর retry-তে backoff করা; datagram পথের MTU-র মধ্যে রাখা; checksum বন্ধ না করা। বিস্তারিত শর্ত RFC-তেই পড়ুন।
৮ · Connected UDP socket, NAT আর firewall
UDP-র “সংযোগ নেই”, তবু UDP socket-এ connect() ডাকা যায় — এবং এটি দরকারি। Connected UDP socket-এ কোনো packet যায় না, কোনো handshake হয় না; kernel শুধু একটি স্থায়ী অপর প্রান্ত মনে রাখে। ফলাফল তিনটি:
- প্রতিবার ঠিকানা না দিয়ে
send()/recv()ব্যবহার করা যায়। - অন্য কোনো ঠিকানা:port থেকে আসা datagram kernel ছেঁকে ফেলে — অচেনা কেউ উত্তর সেজে ঢুকতে পারে না।
- পথ থেকে আসা ICMP ত্রুটি (যেমন port unreachable, পর্ব ২০) প্রোগ্রামকে জানানো হয় — Linux-এ পরের কোনো socket call ত্রুটি
ECONNREFUSEDদেয়। Unconnected socket-এ সাধারণত এই খবর আসে না, কারণ kernel জানে না কোন গন্তব্যের ত্রুটি কোনsendto()-এর।
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
int main(void) {
struct sockaddr_in a = {0};
a.sin_family = AF_INET;
a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
int probe = socket(AF_INET, SOCK_DGRAM, 0); /* find a port that is surely closed: */
socklen_t al = sizeof a; /* bind, read the number, then close it */
bind(probe, (struct sockaddr *)&a, sizeof a);
getsockname(probe, (struct sockaddr *)&a, &al);
close(probe);
puts("target: a closed UDP port on 127.0.0.1 (nobody listening)");
int s = socket(AF_INET, SOCK_DGRAM, 0);
if (connect(s, (struct sockaddr *)&a, sizeof a) < 0) { perror("connect"); return 1; }
puts("connect() on UDP: no packet sent, kernel just remembers the peer");
struct timeval tv = {1, 0}; /* never wait more than 1 s */
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
ssize_t n = send(s, "ping", 4, 0);
printf("send(): %zd bytes (UDP send succeeds - no handshake)\n", n);
char buf[16];
n = recv(s, buf, sizeof buf, 0);
if (n < 0 && errno == ECONNREFUSED)
puts("recv(): ECONNREFUSED - ICMP port unreachable came back");
else
printf("recv(): %s\n", n < 0 ? strerror(errno) : "got data?");
close(s);
return 0;
}আউটপুট:
target: a closed UDP port on 127.0.0.1 (nobody listening)
connect() on UDP: no packet sent, kernel just remembers the peer
send(): 4 bytes (UDP send succeeds - no handshake)
recv(): ECONNREFUSED - ICMP port unreachable came back
প্রোগ্রাম প্রথমে একটি port bind করে নম্বরটা জেনে socket বন্ধ করে দেয় — তাই সেই port-এ এখন কেউ শুনছে না। connect() সফল হলো, যদিও অপর প্রান্তে কেউ নেই: UDP-তে connect মানে শুধু ঠিকানা মনে রাখা। send()-ও সফল — ৪ বাইট “পাঠানো হয়েছে”, কারণ UDP-র কাছে পাঠানো মানে শুধু packet-টি নেটওয়ার্কে ছাড়া। কিন্তু প্রাপক kernel (এখানে একই কম্পিউটার) ICMP port unreachable ফেরত দিল, আর connected socket হওয়ায় পরের recv() সেটি ECONNREFUSED হিসেবে জানাল। বাস্তব নেটওয়ার্কে এই ICMP firewall-এ আটকাতে বা rate limit-এ বাদ পড়তে পারে; তাই কোডে এক সেকেন্ডের timeout রাখা হয়েছে, যাতে কিছুই না এলে প্রোগ্রাম আটকে না থাকে। এই কৌশলের একটি জনপ্রিয় ব্যবহার: “কোন interface দিয়ে বেরোব?” জানতে UDP socket connect করে getsockname() ডাকা — কোনো packet না পাঠিয়েই kernel-এর routing সিদ্ধান্ত জানা যায়।
NAT আর stateful firewall-এর চোখে UDP
UDP-তে শুরু-শেষের কোনো চিহ্ন নেই (TCP-র SYN বা FIN-এর মতো), তাই NAT আর stateful firewall একটি UDP “কথোপকথন”-কে চেনে শুধু 5-tuple দিয়ে, আর শেষ হয়েছে ধরে নেয় একটি নির্দিষ্ট সময় নীরবতার পর। এই timeout TCP-র চেয়ে অনেক ছোট — কিছু যন্ত্রে মাত্র কয়েক দশ সেকেন্ড। ফল: অনেকক্ষণ চুপ থাকা UDP অ্যাপের পরের উত্তর NAT-এ আটকে যায়। প্রতিকার নিয়মিত keepalive — যেমন WireGuard-এ NAT-এর পেছনের peer-এর জন্য PersistentKeepalive (প্রায়ই ২৫ সেকেন্ড পরামর্শ দেওয়া হয়)। NAT-এর UDP timeout-এর মান, Linux conntrack-এর ডিফল্ট আর UDP hole punching পর্ব ১৯-এ (NAT) বিস্তারিত দেখেছেন — এখানে আর পুনরাবৃত্তি করছি না।
আরেকটি বাস্তবতা: কিছু অফিস বা হোটেলের firewall DNS ছাড়া প্রায় সব UDP আটকায়। তাই ব্রাউজার QUIC কাজ না করলে TCP-র HTTP/2-তে ফিরে যায়, আর ভিডিও কলের অ্যাপ TURN relay-কে TCP বা TLS port 443-এ চালায় — UDP-ভিত্তিক নকশায় এই বিকল্প পথ রাখাই নিয়ম।
৯ · UDP-র অপব্যবহার — reflection আর amplification, আর তার প্রতিরক্ষা
UDP-তে handshake নেই — সার্ভার উত্তর পাঠায় datagram-এর উৎস ঠিকানায়, যাচাই ছাড়াই। আর IP header-এর উৎস ঠিকানা প্রেরক নিজেই লেখে। যে নেটওয়ার্ক বাইরে যাওয়া packet-এর উৎস যাচাই করে না, সেখান থেকে অন্যের ঠিকানা বসিয়ে packet পাঠানো সম্ভব — IP spoofing. এই দুটি মিলে তৈরি হয় এক ধরনের DDoS, যা গত এক দশকে সবচেয়ে বড় আক্রমণগুলোর অনেকটির পেছনে ছিল। এখানে ব্যাপারটি শুধু ধারণা আর প্রতিরক্ষার দিক থেকে দেখি।
Reflection
আক্রমণকারী শিকারের ঠিকানাকে উৎস বানিয়ে হাজারো নিরীহ সার্ভারে প্রশ্ন পাঠায়। সার্ভারগুলো সরল বিশ্বাসে উত্তর পাঠায় শিকারকে (reflection). শিকার দেখে ট্রাফিক আসছে বৈধ সার্ভার থেকে; আসল উৎস লুকানো।
জাল উৎসAmplification
যদি ছোট প্রশ্নের উত্তর অনেক বড় হয়, তাহলে আক্রমণকারীর পাঠানো প্রতি বাইট শিকারের কাছে বহুগুণ হয়ে পৌঁছায় (amplification). দুর্বলতা protocol-এর নকশায়, কোনো “bug”-এ নয়।
ছোট প্রশ্ন, বড় উত্তরযেমন ৬০ বাইটের প্রশ্নে ৩,০০০ বাইটের উত্তর → ৫০ গুণ; আক্রমণকারী ১ Gbps পাঠালে শিকারের দিকে প্রায় ৫০ Gbps
ইতিহাসে কয়েকটি পরিচিত উদাহরণ (সংখ্যাগুলো প্রকাশিত রিপোর্টের, আনুমানিক): ২০১৩ সালে একটি স্প্যাম-বিরোধী সংস্থার ওপর বিশাল আক্রমণে বিপুল সংখ্যক open resolver — পৃথিবীর যে-কারও প্রশ্নের উত্তর দেওয়া DNS সার্ভার — ব্যবহার হয়েছিল; ২০১৪ সালের দিকে পুরোনো NTP সার্ভারের একটি পর্যবেক্ষণ-কমান্ড কয়েকশো গুণ amplification দিয়ে ব্যাপক অপব্যবহৃত হয়; আর ফেব্রুয়ারি ২০১৮-এ ইন্টারনেটে খোলা memcached সার্ভার (data center-এর ভেতরের cache, বাইরে খোলা থাকার কথাই নয়) দিয়ে GitHub-এর ওপর প্রায় ১.৩৫ Tbps-এর আক্রমণ হয়, যেখানে amplification হাজার গুণেরও বেশি বলে জানানো হয়েছিল।
প্রতিরক্ষা — কে কী করতে পারেন
| কে | কী করেন | কেন কাজ করে |
|---|---|---|
| ISP আর নেটওয়ার্ক প্রশাসক | Ingress filtering — BCP 38 (RFC 2827, ২০০০): গ্রাহকের দিক থেকে আসা packet-এর উৎস ঠিকানা সেই গ্রাহকের ঠিকানা-ব্লকের কি না যাচাই; না হলে ফেলে দেওয়া | জাল উৎস-ঠিকানাই reflection-এর ভিত্তি; উৎসেই আটকালে আক্রমণ হয় না। সমস্যা: সব নেটওয়ার্ক এটি করলেই পুরো সুরক্ষা |
| DNS সার্ভার চালান যাঁরা | Recursive resolver শুধু নিজের গ্রাহক বা নিজের নেটওয়ার্কের জন্য খোলা রাখা; authoritative সার্ভারে Response Rate Limiting | Open resolver না থাকলে প্রতিফলক কমে; RRL একই ঠিকানায় বারবার একই বড় উত্তর পাঠানো সীমিত করে |
| সেবা ও সফটওয়্যার নির্মাতা | অপ্রয়োজনীয় UDP সেবা বন্ধ; ডিফল্ট শুধু ভেতরের ঠিকানায় শোনা; অপব্যবহৃত কমান্ড সরানো | যেমন আধুনিক NTP সফটওয়্যারে পুরোনো সেই তালিকা-কমান্ড বন্ধ, আর memcached ২০১৮ সালের সংস্করণ ১.৫.৬ থেকে ডিফল্টভাবে UDP বন্ধ রাখে |
| Protocol নকশাকারী | ঠিকানা যাচাইয়ের আগে উত্তর প্রশ্নের চেয়ে খুব বড় না করা; cookie বা token দিয়ে ঠিকানা যাচাই | QUIC-এর anti-amplification limit: যাচাইয়ের আগে সার্ভার পাওয়া বাইটের তিন গুণের বেশি পাঠায় না; ক্লায়েন্টের প্রথম datagram ১,২০০ বাইট হওয়ার নিয়মও এতে সাহায্য করে |
| শিকার ও তার ISP | Upstream-এ ছেঁকে ফেলা (scrubbing সেবা, anycast-এ ছড়ানো CDN), অপ্রত্যাশিত উৎস-port-এর UDP-তে rate limit | নিজের link ভরে গেলে নিজের firewall কিছুই করতে পারে না — ছাঁকতে হয় আগেই, বড় নেটওয়ার্কে |
এই আলোচনা শুধু বোঝা আর প্রতিরক্ষার জন্য। অন্যের নেটওয়ার্কে স্ক্যান করা বা জাল ঠিকানায় packet পাঠানো অনৈতিক আর আইনত দণ্ডনীয় হতে পারে। শুধু নিজের দায়িত্বের সার্ভার যাচাই করুন — যেমন ss -ulpn দিয়ে দেখুন কোন UDP সেবা কোন interface-এ শুনছে।
আরও গভীরে: Linux-এ UDP datagram-এর পথের প্রতিটি ধাপে নীরব ক্ষতি সম্ভব, আর তার হিসাব থাকে /proc/net/snmp-এর Udp: সারিতে — InErrors, RcvbufErrors (socket-এর receive buffer ভরা থাকায় বাদ), InCsumErrors (checksum মেলেনি), NoPorts (কেউ শুনছে না, ICMP গেছে)। দ্রুত UDP সার্ভার তাই SO_RCVBUF বড় করে আর recvmmsg()/sendmmsg() দিয়ে একবারে অনেক datagram পড়ে-লেখে; QUIC বাস্তবায়নগুলো UDP GSO (UDP_SEGMENT) দিয়ে একটি বড় buffer kernel-কে দিয়ে অনেক datagram-এ ভাগ করায়। IPv6-এ tunnel-এর মতো বিশেষ ক্ষেত্রে checksum 0 রাখার সীমিত অনুমতি আছে (RFC 6935/6936), আর UDP-Lite (RFC 3828) checksum দিয়ে ডেটার শুধু একটি অংশ ঢাকতে দেয় — দুটোরই প্রচলন সীমিত।
পরীক্ষা ও ইন্টারভিউতে এই বিষয়ে প্রায়ই যা জিজ্ঞেস করা হয়:
- UDP header-এ কয়টি ঘর, আকার কত? — চারটি ১৬ bit-এর ঘর (source port, destination port, length, checksum), মোট ৮ বাইট।
- TCP-র byte stream আর UDP-র message boundary-র পার্থক্য? — UDP-তে একটি sendto = একটি datagram = একটি recvfrom; TCP-তে send-এর সীমানা থাকে না, অ্যাপকে নিজে framing করতে হয়।
- IPv4-এ UDP-র সর্বোচ্চ payload? — ৬৫,৫৩৫ − ২০ − ৮ = ৬৫,৫০৭ বাইট; বাস্তবে fragmentation এড়াতে ১,৪৭২ বা তার কম।
- DNS কেন UDP ব্যবহার করে, আর কখন TCP? — একটি প্রশ্ন-উত্তরে handshake বাঁচে; উত্তর বড় হলে (TC bit) বা zone transfer-এ TCP.
- UDP amplification কী, প্রধান প্রতিরক্ষা কী? — জাল উৎস-ঠিকানায় ছোট প্রশ্ন, বড় উত্তর শিকারের দিকে; BCP 38 ingress filtering, open resolver বন্ধ, rate limit.
১০ · AI-এর যুগে UDP — কোড, সংখ্যা আর নিরাপত্তার পরামর্শ যাচাই
“একটি UDP সার্ভার লিখে দাও” — AI কয়েক সেকেন্ডে কোড দেবে, আর loopback-এ তা চলবেও। সমস্যা দেখা দেয় বাস্তব নেটওয়ার্কে: loss, বড় datagram, NAT, firewall. UDP নিয়ে AI-এর পরিচিত ভুলগুলো:
| AI-এর ভুল | কেন ভুল | কীভাবে ধরবেন |
|---|---|---|
| “UDP-তে checksum নেই” বা “UDP-র সর্বোচ্চ payload ৬৫,৫৩৫” | Checksum আছে (IPv6-এ বাধ্যতামূলক); IPv4 payload সর্বোচ্চ ৬৫,৫০৭ | RFC 768 নিজে পড়ুন — তিন পাতার; হিসাব নিজে করুন |
| UDP-তে TCP-র মতো “সব বাইট পড়া পর্যন্ত” লুপ, বা ছোট buffer | একটি recvfrom = একটি datagram; বাকি অংশ হারায় | man 7 udp, MSG_TRUNC পরীক্ষা (অংশ ২-এর কোড) |
Timeout ছাড়া recvfrom() | একটি হারানো উত্তরে প্রোগ্রাম চিরকাল আটকে থাকে | কোডে SO_RCVTIMEO, poll বা select আছে কি না দেখুন |
| বিরতিহীন দ্রুত retry, backoff নেই | ভিড়ের নেটওয়ার্কে ভিড় বাড়ায়; RFC 8085-এর বিরোধী | Retry-র সময় দ্বিগুণ হচ্ছে কি না, সর্বোচ্চ চেষ্টার সীমা আছে কি না দেখুন |
| “UDP কাজ করছে না? Firewall-এ সব UDP খুলে দিন” | অপ্রয়োজনীয় সেবা ইন্টারনেটে উন্মুক্ত হয়, amplification-এর প্রতিফলক হতে পারে | শুধু দরকারি একটি port, নির্দিষ্ট উৎস থেকে; ss -ulpn দিয়ে কে শুনছে দেখুন |
netstat -su, ifconfig-কে একমাত্র পথ বলা | আধুনিক Linux-এ ss, ip, nstat; অনেক distro-তে net-tools ডিফল্ট থাকে না | man ss, /proc/net/snmp নিজে পড়ুন (পর্ব ০৬) |
| বানানো RFC নম্বর বা ভুল সাল | ভাষার মডেল বিশ্বাসযোগ্য শোনানো নম্বর বানাতে পারে | rfc-editor.org-এ শিরোনাম মেলান: UDP 768, UDP নির্দেশিকা 8085, EDNS 6891, QUIC 9000, BCP 38 = RFC 2827 |
AI-কে UDP কোড লিখতে দিলে বাস্তব নেটওয়ার্কের শর্তগুলো আগেই বলে দিন:
AI-এর UDP পরামর্শ ব্যবহারের আগে — চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১শুরুতে TCP ছিল একটিই protocol. কণ্ঠ পাঠানোর অভিজ্ঞতা কীভাবে TCP আর IP-কে আলাদা করল, আর UDP-র জন্ম হলো — এই সিদ্ধান্ত আজও কেন গুরুত্বপূর্ণ?
১৯৭৪ সালে Vint Cerf আর Bob Kahn যে “Transmission Control Program”-এর প্রস্তাব দেন, তাতে ঠিকানা দিয়ে packet পৌঁছানো আর নির্ভরযোগ্যভাবে ক্রম মেনে ডেটা দেওয়া — দুটি কাজই এক protocol-এ ছিল। সে সময়ের প্রায় সব ব্যবহার — ফাইল পাঠানো, দূরের কম্পিউটারে login — হারানো বাইট সহ্য করে না, তাই এটি স্বাভাবিক মনে হয়েছিল। কিন্তু packet নেটওয়ার্কে কণ্ঠ পাঠানোর পরীক্ষা করা গবেষকেরা দেখান: কণ্ঠের একটি টুকরো আবার পাঠানোর অপেক্ষা মানে পুরো কথা থেমে যাওয়া — একটুখানি ফাঁক কানে প্রায় ধরা পড়ে না, আধা সেকেন্ডের থামা পড়ে। এই ধরনের প্রয়োগের জন্য দরকার ছিল এমন একটি সেবা, যা শুধু পৌঁছানোর চেষ্টা করবে, নিশ্চয়তা দেবে না।
এই যুক্তি আর নেটওয়ার্কের স্তর সাজানোর বৃহত্তর আলোচনার ফলে ১৯৭৮ সালের দিকে নকশা দুই ভাগ হয়: নিচে IP — শুধু ঠিকানা আর best-effort পৌঁছানো; ওপরে TCP — নির্ভরযোগ্যতা। আর যাঁদের TCP-র নিশ্চয়তা লাগবে না, তাঁদের জন্য IP-র ওপর সবচেয়ে পাতলা একটি স্তর: port আর checksum — ১৯৮০ সালে RFC 768-এ UDP. লক্ষ করুন, UDP আসলে “TCP-র খারাপ সংস্করণ” নয়; এটি হলো IP-কে অ্যাপের কাছে প্রায় যেমন আছে তেমন পৌঁছে দেওয়ার একটি দরজা।
এই বিভাজন আজও ইন্টারনেটের উদ্ভাবনের ভিত্তি। VoIP, ভিডিও কল, অনলাইন গেম, আর সবচেয়ে বড় উদাহরণ QUIC — সবই সম্ভব হয়েছে কারণ নতুন কিছু বানাতে চাইলে কাউকে TCP বদলাতে হয় না; UDP-র ওপর নিজের নিয়ম বানালেই হয়। পরে Saltzer, Reed আর Clark-এর end-to-end যুক্তি (১৯৮৪) এই দর্শনকে তাত্ত্বিক রূপ দেয়: যে নিশ্চয়তা শুধু অ্যাপই ঠিকভাবে বোঝে, তা নিচের স্তরে চাপিয়ে দিলে সবার খরচ বাড়ে, অথচ সবার কাজে লাগে না। যদি TCP আর IP আলাদা না হতো, প্রতিটি নতুন real-time প্রয়োগকে হয় অনুপযুক্ত নিশ্চয়তা বহন করতে হতো, নয়তো মূল protocol বদলাতে হতো — যা কোটি কোটি যন্ত্রে প্রায় অসম্ভব।
মূল উপলব্ধি: ভালো স্তর-নকশা মানে প্রতিটি স্তর শুধু ততটুকু দেবে যতটুকু প্রায় সবার লাগে। UDP সেই “প্রায় কিছুই না”-র স্তর, আর ঠিক এজন্যই এটি নতুন ধারণার জন্য সবচেয়ে উর্বর জমি।
প্র ০২QUIC নির্ভরযোগ্যতা, ক্রম আর congestion control — প্রায় সবই TCP-র মতো দেয়। তাহলে এটি নতুন একটি IP protocol না হয়ে UDP-র ওপর কেন, আর এর দাম কী?
তাত্ত্বিকভাবে QUIC IP-র ওপর নিজস্ব protocol নম্বর নিয়ে চলতে পারত, যেমন TCP চলে ৬ নম্বরে আর UDP ১৭-তে। কিন্তু বাস্তবের ইন্টারনেট কয়েক দশকে “জমে” গেছে (ossification). বাসার NAT, অফিসের firewall, মোবাইল অপারেটরের middlebox — এগুলো সাধারণত শুধু TCP আর UDP চেনে; অচেনা protocol নম্বরের packet অনেক জায়গায় সোজা ফেলে দেয়, কারণ NAT জানেই না port কোথায়। SCTP-র অভিজ্ঞতা এর প্রমাণ: ২০০০ সালে মান হওয়া এই transport protocol টেলিফোন নেটওয়ার্কের ভেতরে চললেও সাধারণ ইন্টারনেটে কখনো ছড়াতে পারেনি। UDP-র ওপর বসলে QUIC প্রায় সব middlebox পার হয়, কারণ বাইরে থেকে এটি একটি সাধারণ UDP datagram.
দ্বিতীয় কারণ হালনাগাদের গতি। TCP থাকে OS-এর kernel-এ — তার আচরণ বদলাতে হলে কোটি কোটি ফোন আর কম্পিউটারের OS হালনাগাদ লাগে, যা বছরের পর বছর নেয়। QUIC সাধারণত অ্যাপ বা লাইব্রেরির ভেতরে, user space-এ চলে; ব্রাউজার হালনাগাদ হলেই নতুন congestion control বা নতুন বৈশিষ্ট্য সবার কাছে পৌঁছায়। তৃতীয়ত, QUIC তার প্রায় পুরো header এনক্রিপ্ট করে, যাতে middlebox-গুলো ভেতরের কাঠামো দেখে কোনো ধারণা না গড়ে — ভবিষ্যতে protocol বদলালে যেন আবার জমে না যায়।
দাম আছে। UDP-র জন্য kernel-এর পথ বহু বছর TCP-র মতো অপ্টিমাইজ করা হয়নি, তাই শুরুতে QUIC-এ প্রতি বাইটে CPU খরচ বেশি ছিল; UDP GSO, recvmmsg() আর অন্য কৌশল দিয়ে সেই ফাঁক কমানো হয়েছে, কিন্তু পুরোপুরি মেটেনি। কিছু নেটওয়ার্ক UDP আটকায় বা কম গতি দেয়, তাই ব্রাউজারকে TCP-তে ফিরে যাওয়ার পথ রাখতে হয়। Firewall আর নেটওয়ার্ক প্রশাসকেরা এনক্রিপ্টেড QUIC-এর ভেতরে কিছু দেখতে পান না, ফলে সমস্যা খোঁজা কঠিন হয়। আর NAT-এর ছোট UDP timeout QUIC-কে নিয়মিত keepalive পাঠাতে বাধ্য করে।
মূল উপলব্ধি: QUIC UDP-কে বেছে নিয়েছে প্রযুক্তিগত সৌন্দর্যের জন্য নয়, বাস্তবতার জন্য — এটিই একমাত্র পথ যা আজকের ইন্টারনেটে নতুন transport সবার কাছে পৌঁছাতে পারে। UDP এখানে একটি “সার্বজনীন খাম”, যার ভেতরে যেকোনো নতুন protocol বসানো যায় (বিস্তারিত পর্ব ৩৩)।
প্র ০৩DNS-এর নিরাপত্তার ইতিহাস অনেকটাই UDP-র “যাচাই ছাড়া উত্তর গ্রহণ”-এর ইতিহাস। ২০০৮ সালের Kaminsky দুর্বলতা কী দেখিয়েছিল, আর সমাধানগুলো UDP-র কোন বৈশিষ্ট্য নিয়ে কাজ করে?
UDP-তে কোনো সংযোগ নেই, তাই একটি resolver কোনো উত্তরকে “আমার প্রশ্নের উত্তর” মেনে নেয় কয়েকটি মিল দেখে: উত্তর এসেছে যে সার্ভারকে জিজ্ঞেস করা হয়েছিল তার ঠিকানা থেকে, ঠিক যে port থেকে প্রশ্ন গিয়েছিল সেখানে, আর ১৬ bit-এর query ID মেলে। আক্রমণকারী যদি আসল উত্তর আসার আগেই এই মানগুলো অনুমান করে একটি জাল উত্তর পাঠাতে পারে (জাল উৎস-ঠিকানা দিয়ে), resolver সেটি মেনে নিয়ে cache-এ রাখবে — এটিই cache poisoning. অনেক পুরোনো resolver প্রতিটি প্রশ্নের জন্য একই উৎস port ব্যবহার করত, ফলে অনুমান করার বাকি ছিল শুধু ৬৫,৫৩৬টি ID.
২০০৮ সালে নিরাপত্তা গবেষক Dan Kaminsky দেখান যে এই অনুমানের খেলাকে প্রায় সীমাহীনবার খেলা যায়: আক্রমণকারী একটির পর একটি অস্তিত্বহীন উপ-নাম জিজ্ঞেস করিয়ে প্রতিবার নতুন সুযোগ পায়, আর জাল উত্তরে পুরো ডোমেইনের নাম-সার্ভারকেই বদলে দেওয়ার তথ্য ঢুকিয়ে দেয়। প্রধান DNS সফটওয়্যার নির্মাতারা তখন গোপনে সমন্বয় করে একই সময়ে হালনাগাদ প্রকাশ করেন।
তাৎক্ষণিক সমাধান ছিল UDP-র নিজের ঘরকেই বাড়তি গোপন সংখ্যা হিসেবে ব্যবহার: প্রতিটি প্রশ্নে এলোমেলো উৎস port (source port randomization). এতে ID-র ১৬ bit-এর সাথে port-এর আরও প্রায় ১৫–১৬ bit যোগ হয়, অনুমান কয়েক হাজার গুণ কঠিন হয় — পরে RFC 5452 এই পরামর্শগুলো লিপিবদ্ধ করে। কিন্তু এটি সম্ভাবনা কমায়, সমস্যা দূর করে না; NAT যদি port বদলে ক্রমিক করে দেয়, সুরক্ষা আবার কমে। দীর্ঘমেয়াদি সমাধান DNSSEC — উত্তরে cryptographic স্বাক্ষর, যাতে জাল উত্তর যতই নিখুঁত সময়ে আসুক, স্বাক্ষর মিলবে না। আবার DNSSEC উত্তর বড় করে, ফলে UDP-র আকারের সমস্যা আর amplification-এর ঝুঁকি বাড়ে — অংশ ৫ আর ৯-এর আলোচনা এখানে মিলে যায়। পাশাপাশি এখন অনেক ক্লায়েন্ট DNS over TLS বা HTTPS ব্যবহার করে, যেখানে UDP-র বদলে এনক্রিপ্টেড সংযোগ।
মূল উপলব্ধি: সংযোগহীন protocol-এ “কে উত্তর দিল” যাচাইয়ের দায় পুরোপুরি অ্যাপের। ID আর এলোমেলো port হলো দুর্বল প্রমাণ, স্বাক্ষর বা এনক্রিপ্টেড সংযোগ হলো শক্ত প্রমাণ — আর প্রতিটি সমাধান নতুন কোনো খরচ নিয়ে আসে।
প্র ০৪BCP 38 প্রকাশ হয়েছে ২০০০ সালে, তবু দুই দশক পরেও জাল উৎস-ঠিকানার amplification আক্রমণ হচ্ছে। প্রযুক্তি জানা থাকা সত্ত্বেও সমস্যা কেন টিকে আছে?
Ingress filtering প্রযুক্তিগতভাবে সহজ: ISP জানে প্রতিটি গ্রাহককে কোন ঠিকানা-ব্লক দেওয়া হয়েছে, তাই সেই গ্রাহকের দিক থেকে অন্য কোনো উৎস-ঠিকানার packet এলে ফেলে দিলেই হয়। আধুনিক router-এ এর জন্য uRPF-এর মতো সরল বৈশিষ্ট্যও আছে — packet যে interface দিয়ে এসেছে, উৎস ঠিকানার দিকে ফেরার পথও কি সেই interface? তাহলে সমস্যা কোথায়?
মূল সমস্যা অর্থনীতির, যাকে অর্থনীতিবিদেরা “externality” বলেন। একটি ISP যখন filtering চালু করে, তার লাভ প্রায় কিছুই হয় না — তার নিজের গ্রাহকেরা আগের মতোই অন্য নেটওয়ার্ক থেকে আসা আক্রমণের শিকার হতে পারেন। লাভ হয় বাকি পৃথিবীর: ওই নেটওয়ার্ক থেকে আর জাল packet বেরোবে না। খরচ (কনফিগারেশন, multihomed গ্রাহকের জটিলতা, ভুল নিয়মে বৈধ ট্রাফিক আটকে গ্রাহকের অভিযোগ) সবটাই ISP-র। ফলে যতক্ষণ পৃথিবীর কিছু নেটওয়ার্ক filtering না করে, আক্রমণকারীরা সেখান থেকেই জাল packet পাঠাতে পারে — একটি দুর্বল কড়াই পুরো শিকলকে দুর্বল রাখে।
এজন্য প্রতিরক্ষা বহুস্তরে এগিয়েছে। এক, সামাজিক চাপ আর স্বেচ্ছা অঙ্গীকার: MANRS-এর মতো উদ্যোগে নেটওয়ার্ক অপারেটররা প্রকাশ্যে anti-spoofing পালনের প্রতিশ্রুতি দেন, আর গবেষণা প্রকল্পগুলো স্বেচ্ছাসেবকদের সাহায্যে মাপে কোন নেটওয়ার্ক থেকে জাল packet বেরোতে পারে। দুই, প্রতিফলক কমানো: open resolver, পুরোনো NTP, খোলা memcached — এগুলো খুঁজে মালিকদের জানানো আর সফটওয়্যারের ডিফল্ট নিরাপদ করা। তিন, protocol নকশায় শিক্ষা নেওয়া: QUIC-এর ৩ গুণের সীমা, DTLS-এর cookie, DNS-এর RRL — নতুন protocol এখন শুরু থেকেই amplification মাথায় রেখে বানানো হয়। চার, শিকারের দিকে বিশাল ধারণক্ষমতা: বড় CDN আর scrubbing সেবা anycast দিয়ে আক্রমণ বহু data center-এ ছড়িয়ে শোষণ করে।
মূল উপলব্ধি: ইন্টারনেটের নিরাপত্তা অনেক সময় প্রযুক্তির নয়, প্রণোদনার সমস্যা। যেখানে এক পক্ষের খরচে অন্য পক্ষের লাভ, সেখানে মান থাকলেই যথেষ্ট নয় — লাগে সমন্বয়, স্বচ্ছতা আর নকশায় এমন সতর্কতা, যা কারও সদিচ্ছার ওপর নির্ভর করে না।
প্র ০৫“অনলাইন গেম আর ভিডিও কল TCP-তে চালালে সমস্যা কী? নির্ভরযোগ্যতা তো ভালো জিনিস” — head-of-line blocking আর “দেরিতে আসা ডেটার মূল্য” দিয়ে এর উত্তর দিন।
TCP একটি ক্রমবদ্ধ byte stream দেয়: প্রাপক অ্যাপ বাইট ১০০১ পাবে না, যতক্ষণ না বাইট ১০০০ পৌঁছায়। এখন একটি গেম ভাবুন, যা প্রতি সেকেন্ডে ধরুন ৩০ বার প্রতিটি খেলোয়াড়ের অবস্থান পাঠায়। একটি packet হারালে TCP পরের সব packet নিজের buffer-এ আটকে রাখে, প্রেরক হারানো packet আবার পাঠায় — তাতে অন্তত একটি RTT-র বেশি সময়, প্রায়ই আরও বেশি (retransmission timeout). এই পুরো সময় অ্যাপ কিছুই পায় না, যদিও নতুন অবস্থানগুলো আসলে পৌঁছে বসে আছে। খেলোয়াড় দেখেন পর্দা “আটকে” গেল, তারপর হঠাৎ সব একসাথে লাফ দিল। এর নাম head-of-line blocking — সামনের একটি হারানো টুকরো পেছনের সবকিছু আটকে রাখে।
অথচ এই গেমের কাছে হারানো packet-টির কোনো মূল্যই নেই: ৩৩ মিলিসেকেন্ড আগের অবস্থান জেনে কী হবে, যখন এখনকার অবস্থান হাতে? ভিডিও কলেও একই কথা — ২০০ মিলিসেকেন্ড আগের কণ্ঠ এখন বাজালে কথোপকথন এলোমেলো হয়। এই ধরনের ডেটার মূল্য সময়ের সাথে দ্রুত শূন্যে নামে। তাই এদের দরকার “আংশিক নির্ভরযোগ্যতা”: কিছু জিনিস (খেলোয়াড়ের মৃত্যু, চ্যাটের বার্তা, স্কোর) নিশ্চিতভাবে পৌঁছাতে হবে, কিন্তু প্রতি মুহূর্তের অবস্থান বা কণ্ঠের টুকরো হারালে বাদ দেওয়াই ভালো।
UDP এই নমনীয়তা দেয়। গেম ইঞ্জিনগুলো সাধারণত UDP-র ওপর নিজস্ব স্তর বানায়: প্রতিটি packet-এ ক্রমিক নম্বর, প্রাপকের পাঠানো ACK-এর একটি bitmap, আর শুধু “গুরুত্বপূর্ণ” বার্তাগুলো আবার পাঠানো — অবস্থানের মতো ডেটা কখনো নয়, কারণ পরের packet-এই নতুন মান আসছে। ভিডিও কলে RTP sequence number আর timestamp দিয়ে jitter buffer চলে, হারানো টুকরোর জায়গা প্রায়ই অনুমান দিয়ে ভরানো হয় (packet loss concealment). QUIC এর একটি মাঝামাঝি সমাধান দেয়: একাধিক স্বাধীন stream, যাতে একটি stream-এর হারানো ডেটা অন্যগুলোকে আটকায় না, আর datagram extension-এ অনির্ভরযোগ্য বার্তাও পাঠানো যায়।
মূল উপলব্ধি: “নির্ভরযোগ্য” সবসময় “ভালো” নয় — প্রশ্ন হলো কোন ডেটার জন্য কতটা দেরি গ্রহণযোগ্য। যে ডেটার মূল্য দ্রুত ফুরায়, তার জন্য সময়মতো পৌঁছানো নিখুঁতভাবে পৌঁছানোর চেয়ে জরুরি; UDP অ্যাপকে সেই সিদ্ধান্ত নিজে নেওয়ার স্বাধীনতা দেয়।
অনুশীলন
খাতা-কলম নিন — বাইট, শব্দ আর সম্ভাবনা ধরে ধরে হিসাব করুন।
-
হিসাব (আকার আর fragmentation): একটি অ্যাপ IPv4-এ সর্বোচ্চ আকারের UDP datagram (৬৫,৫০৭ বাইট payload) পাঠাল, পথের MTU ১,৫০০ বাইট, IP header-এ কোনো option নেই। (ক) UDP Length ঘরে কত লেখা হবে? (খ) IP-কে কয়টি fragment-এ ভাগ করতে হবে? (গ) প্রতিটি fragment স্বাধীনভাবে ১% সম্ভাবনায় হারালে পুরো datagram পৌঁছানোর সম্ভাবনা কত? (ঘ) একই ডেটা ১,৪৭২ বাইটের datagram-এ ভাগ করে পাঠালে প্রতিটি datagram পৌঁছানোর সম্ভাবনা কত?
(ক) Length = ৮ + ৬৫,৫০৭ = ৬৫,৫১৫ — ১৬ bit-এ আঁটে (৬৫,৫৩৫-এর নিচে)। IP total length হবে ২০ + ৬৫,৫১৫ = ৬৫,৫৩৫, ঠিক সর্বোচ্চ।
(খ) প্রতিটি fragment-এ IP header ২০ বাইট, তাই ডেটার জায়গা ১,৪৮০ বাইট (৮-এর গুণিতক হতে হয়, ১,৪৮০ তা-ই)। IP-র ভাগ করার ডেটা = UDP header + payload = ৬৫,৫১৫ বাইট। ৬৫,৫১৫ ÷ ১,৪৮০ = ৪৪.২৭…, তাই ৪৫টি fragment — ৪৪টি পূর্ণ (৪৪ × ১,৪৮০ = ৬৫,১২০) আর শেষটিতে ৩৯৫ বাইট।
(গ) সবগুলো পৌঁছাতে হবে: ০.৯৯৪৫ ≈ ০.৬৩৬, অর্থাৎ প্রায় ৬৪% — প্রায় তিনবারে একবার পুরো datagram হারায়, যদিও প্রতিটি টুকরোর loss মাত্র ১%! (ঘ) ১,৪৭২ বাইটের datagram fragment হয় না, তাই প্রতিটির সম্ভাবনা ০.৯৯; হারালে শুধু ওই ১,৪৭২ বাইট আবার পাঠাতে হয়, পুরো ৬৫ KB নয়। এজন্যই ছোট datagram আর অ্যাপের নিজের ভাগ করা উত্তম।
-
আউটপুট অনুমান করুন: অংশ ৭-এর retry simulator-এ শুধু
LOSS = 0.4বদলেLOSS = 0করা হলো (বাকি সব একই)। প্রথম প্রশ্নের আউটপুটের লাইনগুলো কী হবে? আর কেন “random” seed থাকলেও ফল নির্দিষ্ট?t=0.00s try 1: send id=0x1a2b, wait 1s -> reply id=0x1a2b answer for abcltech.com after 0.08s (1 try)কোনো loss না থাকলে
rand() < 0কখনো সত্য নয়, তাই প্রশ্ন বা উত্তর হারায় না — প্রথম চেষ্টাতেই উত্তর, সময় শুধু RTT ০.০৮ সেকেন্ড। বাকি দুটি প্রশ্নও একইভাবে এক চেষ্টায় শেষ হবে। ফল নির্দিষ্ট কারণ “এলোমেলো” সংখ্যা আসছে একটি স্থির seed (১৫) থেকে একটি সূত্রে (Park-Miller): একই seed মানে প্রতিবার একই সংখ্যার ক্রম। Simulation আর পরীক্ষায় এটি জরুরি — ফল পুনরুৎপাদন করা যায়। -
হাতে হিসাব (checksum): একটি UDP datagram: উৎস 10.0.0.1:1234, গন্তব্য 10.0.0.2:5678, ডেটা দুটি বাইট
4f 4b(“OK”). (ক) UDP length কত? (খ) Pseudo-header, header আর ডেটার সব ১৬ bit-এর শব্দ লিখুন (checksum ঘর 0 ধরে)। (গ) Checksum বের করুন। (ঘ) এই datagram IPv4-এ checksum ঘরে 0 দিয়ে পাঠালে প্রাপক কী করবে, আর IPv6-এ?(ক) ৮ + ২ = ১০ = 0x000a. (খ) 10.0 =
0a00, 0.1 =0001, 10.0 =0a00, 0.2 =0002, শূন্য + 17 =0011, length000a; header: 1234 =04d2, 5678 =162e, length000a, checksum0000; ডেটা4f4b(জোড় সংখ্যার বাইট, padding লাগে না)।(গ) যোগ: 0a00 + 0001 + 0a00 + 0002 + 0011 + 000a = 141e; + 04d2 = 18f0; + 162e = 2f1e; + 000a = 2f28; + 0000 = 2f28; + 4f4b = 7e73. এখানে কোনো carry হয়নি। উল্টে: ~7e73 = 818c. যাচাই: 7e73 + 818c = ffff ✔. (ঘ) IPv4-এ 0 মানে “checksum হিসাব করা হয়নি” — প্রাপক যাচাই না করেই গ্রহণ করবে। IPv6-এ checksum বাধ্যতামূলক, তাই 0 checksum-এর datagram প্রাপক ফেলে দেবে (tunnel-এর মতো কিছু বিশেষ ক্ষেত্র ছাড়া)।
-
কোড লিখুন: অংশ ২-এর প্রোগ্রামে
recv(..., MSG_TRUNC)Linux-নির্ভর। বহনযোগ্যভাবে কাটা পড়া ধরতেrecvmsg()দিয়ে একটি datagram পড়েmsg_flagsযাচাই করার কয়েক লাইনের কোড লিখুন —rxsocket আর ৮ বাইটেরsmallbuffer ধরে নিন।#include <sys/uio.h> /* struct iovec */ struct iovec iov = { .iov_base = small, .iov_len = sizeof small }; struct msghdr mh = { .msg_iov = &iov, .msg_iovlen = 1 }; /* no source address wanted */ ssize_t n = recvmsg(rx, &mh, 0); if (n < 0) perror("recvmsg"); else if (mh.msg_flags & MSG_TRUNC) printf("datagram truncated: got only %zd bytes\n", n); else printf("complete datagram: %zd bytes\n", n);recvmsg()একটিiovec-এ ডেটা নেয়; ফেরার পর kernelmsg_flags-এ জানায় কী ঘটেছে। ১০০ বাইটের datagram-এ এটি ছাপবেdatagram truncated: got only 8 bytes. এখানেnহলো কপি করা বাইট (৮), আসল দৈর্ঘ্য নয় — আসল দৈর্ঘ্য জানতে Linux-এ ফ্ল্যাগ আর্গুমেন্টেও MSG_TRUNC দেওয়া যায়। প্রেরকের ঠিকানাও চাইলেmsg_nameআরmsg_namelen-এ একটিsockaddr_inদিন। বাস্তবের পরামর্শ: buffer প্রত্যাশিত সবচেয়ে বড় datagram-এর চেয়ে অন্তত এক বাইট বড় রাখলেও কাটা পড়া ধরা যায় — ফল buffer-এর পুরো আকার হলে সন্দেহ করুন।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমIPv4-এ একটি UDP datagram-এর checksum হিসাবের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- উৎস IP, গন্তব্য IP, শূন্য বাইট + protocol 17 আর UDP length দিয়ে pseudo-header বানানো
- UDP header লেখা, checksum ঘর আপাতত 0 রেখে
- ডেটার বাইট সংখ্যা বিজোড় হলে শেষে একটি শূন্য বাইট জুড়ে ১৬ bit-এর শব্দে ভাগ করা
- সব শব্দ one's complement-এ যোগ, ১৬ bit পেরোনো carry আবার নিচে যোগ করে
- যোগফলের সব bit উল্টে checksum ঘরে বসানো; ফল 0 হলে 0xffff পাঠানো
একটি প্রোগ্রাম UDP socket দিয়ে পরপর তিনটি sendto()-তে ২০, ৩০ আর ৫০ বাইট পাঠাল, কিছুই হারাল না, আর প্রাপক ১,০০০ বাইটের buffer দিয়ে recvfrom() ডাকল। প্রথম recvfrom() কত বাইট ফেরত দেবে?
- ১০০ বাইট — তিনটি একসাথে
- ২০ বাইট — শুধু প্রথম datagram
- ১,০০০ বাইট পূর্ণ না হওয়া পর্যন্ত অপেক্ষা করবে
- অনির্দিষ্ট — যেকোনো আকারের টুকরো
IPv4-এ (IP option ছাড়া) একটি UDP datagram-এ সর্বোচ্চ কত বাইট ব্যবহারকারীর ডেটা রাখা যায়?
- ৬৫,৫৩৫
- ৬৫,৫২৭
- ৬৫,৫০৭
- ১,৪৭২
IPv4-এ একটি UDP datagram-এর checksum ঘরে 0x0000 এল। প্রাপকের জন্য এর অর্থ কী?
- প্রেরক checksum হিসাব করেনি — যাচাই ছাড়াই গ্রহণ
- Checksum ভুল — datagram বাতিল
- হিসাবের ফল সত্যিই শূন্য হয়েছিল
- IPv4-এ 0 checksum সবসময় নিষিদ্ধ, তাই বাতিল
একটি UDP socket-এ connect() ডেকে 127.0.0.1-এর একটি বন্ধ port-এ send() করা হলো (sendto নয়)। Linux-এ সাধারণত কী ঘটে?
- connect() নিজেই ECONNREFUSED দিয়ে ব্যর্থ হয়
- send() ব্যর্থ হয়, কারণ handshake হয়নি
- কিছুই জানা যায় না — datagram নীরবে হারায়
- connect() আর send() সফল, পরের socket call-এ ECONNREFUSED আসে
একটি UDP-ভিত্তিক সেবায় ৫০ বাইটের অনুরোধে ২,৫০০ বাইটের উত্তর আসে। একজন আক্রমণকারী জাল উৎস-ঠিকানা দিয়ে মোট ২ Gbps অনুরোধ পাঠালে শিকারের দিকে মোটামুটি কত ট্রাফিক যেতে পারে, আর উৎসেই এটি ঠেকানোর মূল ব্যবস্থা কী?
- ৫০ Mbps; শিকারের firewall
- প্রায় ১০০ Gbps; উৎস নেটওয়ার্কে BCP 38 ingress filtering
- ২ Gbps-এর কম; TCP-তে বদলানো
- ২,৫০০ Gbps; UDP checksum চালু রাখা
নিচের কোনটি UDP-র ওপর চলার সবচেয়ে মৌলিক কারণ, যা TCP দিয়ে একেবারেই করা যায় না?
- Header ছোট বলে প্রতি packet-এ কয়েক বাইট বাঁচে
- UDP-তে checksum আছে, TCP-তে নেই
- একটি datagram একসাথে একটি multicast group-এর সব সদস্যকে বা LAN-এ broadcast-এ পাঠানো
- UDP datagram সবসময় পাঠানোর ক্রমে পৌঁছায়
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- UDP = IP + port + checksum. সংযোগ, নিশ্চয়তা, ক্রম, flow আর congestion control নেই; header ৮ বাইট (source port, destination port, length, checksum). RFC 768, ১৯৮০.
- Datagram-এর সীমানা অক্ষুণ্ণ: একটি sendto = একটি recvfrom. ছোট buffer-এ বাকি অংশ হারায় (MSG_TRUNC দিয়ে ধরা যায়); TCP byte stream, সেখানে সীমানা অ্যাপকে বানাতে হয়।
- Checksum: pseudo-header (উৎস IP, গন্তব্য IP, 0 + 17, length) + header + ডেটা, one's complement যোগ, উল্টানো; প্রাপকের যোগফল ffff হলে ঠিক। IPv4-এ 0 = নেই, IPv6-এ বাধ্যতামূলক।
- আকার: তত্ত্বে IPv4-এ ৬৫,৫০৭ বাইট, বাস্তবে ১,২০০–১,৪৭২; fragmentation-এ একটি টুকরো হারালে পুরো datagram যায়। DNS: ৫১২ → EDNS → ১,২৩২ বাইটের পরামর্শ।
- অ্যাপের দায় আর নিরাপত্তা: timeout, backoff-সহ retry, ID ও sequence number, jitter buffer, congestion control (RFC 8085); connected socket ICMP ত্রুটি জানায়; amplification ঠেকাতে BCP 38, open resolver বন্ধ, rate limit আর নকশায় anti-amplification সীমা।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ২৯ · TCP সংযোগ — three-way handshake, state আর বন্ধ করাপরবর্তী পর্বUDP-র সাথে তুলনা করে TCP-র সংযোগ কীভাবে গড়ে ওঠে।
- পর্ব ২৭ · Transport layer — port, socket আর multiplexingআগের পর্বPort, socket আর demultiplexing-এর পুরো ছবি।
- পর্ব ০৯ · Error detection আর correction — parity, checksum, CRC আর Hammingভিত্তিInternet checksum-এর গণিত আর তার সীমা।
- পর্ব ৩৩ · QUIC আর আধুনিক transport — UDP-র ওপর নতুন TCPসামনেUDP-র ওপর পুরো নির্ভরযোগ্য, এনক্রিপ্টেড transport.
- কম্পিউটার নেটওয়ার্ক · কোর্স হোম৬২টি পর্বDNS (পর্ব ৩৫), real-time streaming (পর্ব ৪০), UDP socket প্রোগ্রামিং (পর্ব ৪২) পর্যন্ত পুরো রোডম্যাপ।
- অপারেটিং সিস্টেম · IPCপাশের ট্র্যাকSocket আর প্রক্রিয়ার মধ্যে বার্তা বিনিময় — OS-এর দিক থেকে।