पिछले लेख में, हमने XDP का उपयोग करके ICMP इको प्रतिक्रिया को वापस करने के लिए एक कार्यक्रम लिखा।
ICMP इको प्रतिक्रिया को वापस करने के लिए कार्यक्रम को कार्यान्वित किया गया, लेकिन यह पता चला कि ICMP का चेकसम ठीक से गणना नहीं किया गया था।
इस बार, ICMP के चेकसम की गणना और इसे सही ढंग से वापस करने के तरीके के बारे में लिखना चाहता हूं।
ICMP का चेकसम
- ICMP हेडर संरचना
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | प्रकार | कोड | चेकसम | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | पहचान | अनुक्रम संख्या | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | डेटा | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
ICMP हेडर में चेकसम शामिल है, और ICMP इको अनुरोध से प्रकार को बदलकर ICMP इको प्रतिक्रिया में बदलने के कारण हमें चेकसम के मान को फिर से गणना करने की आवश्यकता है।
ICMP के चेकसम की गणना हेडर और डेटा की पूरी जानकारी को 16-बिट यूनिट में जोड़कर की जाती है, और यदि कैर्रीओवर होता है, तो उस मान को और भी जोड़ा जाता है। अंत में, 1 के पूरक (बिट उल्टा) को लेकर अंतिम चेकसम का निर्धारण किया जाता है।
इस गणना के तरीके का विवरण RFC 1624 में विस्तृत रूप से दिया गया है। RFC 1141 का संदर्भ लेते समय सावधान रहें क्योंकि अंतर की गणना ठीक से काम नहीं कर रही है। RFC 1141 में अंतर की गणना के वर्णन में गलती है, इसलिए इसे RFC 1624 में संशोधित किया गया है।
इससे पहले मैंने RFC 1141 को देखा और अमरता से समय बर्बाद करने का अनुभव किया।
RFC को अनुवादित किए जाने वाले किसी और के द्वारा नहीं, बल्कि हमेशा मूल पाठ को पढ़ना चाहिए… (अपने लिए एक नसीहत)
16 बिट के अनुसार जोड़ते हुए, अंत में 1 का पूरक लेकर गणना की जाती है, इसलिए वास्तव में 16-बिट यूनिट में केवल संशोधन स्थलों का अंतर लेना ही पर्याप्त है, सभी डेटा की पुनः गणना की आवश्यकता नहीं है।
bpf_csum_diff
eBPF के कार्यक्रम में चेकसम की गणना करते समय bpf_csum_diff
नामक एक फ़ंक्शन प्रदान किया गया है। इस फ़ंक्शन का उपयोग करके चेकसम की गणना आसानी से की जा सकती है।
bpf_csum_diff
निर्दिष्ट मेमोरी क्षेत्र के 16-बिट यूनिट के बदलाव के आधार पर चेकसम के परिवर्तन को गणना करने वाला फ़ंक्शन है। इससे ICMP या IP के चेकसम को प्रभावी ढंग से अपडेट किया जा सकता है।
ICMP इको प्रतिक्रिया लौटाने के लिए कार्यक्रम
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <arpa/inet.h>
static __always_inline __u16 csum_fold(__u32 csum)
{
// उच्च 16 बिट के मान को निम्न 16 बिट में जोड़ना
csum = (csum & 0xffff) + (csum >> 16);
// एक बार फिर कैर्रीओवर को जोड़ना
csum = (csum & 0xffff) + (csum >> 16);
// 1 के पूरक को लेकर अंतिम चेकसम लौटाना
return (__u16)~csum;
}
static __always_inline void swap_eth_addr(__u8 *a, __u8 *b)
{
__u8 tmp[ETH_ALEN];
__builtin_memcpy(tmp, a, ETH_ALEN);
__builtin_memcpy(a, b, ETH_ALEN);
__builtin_memcpy(b, tmp, ETH_ALEN);
}
static __always_inline void swap_ip_addr(__u32 *a, __u32 *b)
{
__u32 tmp = *a;
*a = *b;
*b = tmp;
}
SEC("xdp")
int xdp_echo_reply(struct xdp_md *ctx)
{
void *data_end = (void *)(unsigned long)ctx->data_end;
void *data = (void *)(unsigned long)ctx->data;
// ईथरनेट हेडर का पॉइंटर प्राप्त करना
struct ethhdr *eth = data;
if ((void *)eth + sizeof(*eth) > data_end)
return XDP_PASS;
// ईथरनेट फ्रेम में IP पैकेट है, यह सुनिश्चित करना
if (eth->h_proto != htons(ETH_P_IP))
return XDP_PASS;
// IP हेडर का पॉइंटर प्राप्त करना
struct iphdr *ip = data + sizeof(*eth);
if ((void *)ip + sizeof(*ip) > data_end)
return XDP_PASS;
// सुनिश्चित करना कि प्रोटोकॉल ICMP है
if (ip->protocol != IPPROTO_ICMP)
return XDP_PASS;
// ICMP हेडर का पॉइंटर प्राप्त करना
struct icmphdr *icmp = (void *)ip + sizeof(*ip);
if ((void *)icmp + sizeof(*icmp) > data_end)
return XDP_PASS;
// ICMP इको अनुरोध के अलावा अन्य पैकेट पास करना
if (icmp->type != ICMP_ECHO)
return XDP_PASS;
// स्रोत और लक्ष्य MAC पते को स्वैप करना
swap_eth_addr(eth->h_dest, eth->h_source);
// स्रोत और लक्ष्य IP पते को स्वैप करना
swap_ip_addr(&ip->saddr, &ip->daddr);
// icmp की प्रतिलिपि बनाई जा रही है
struct icmphdr icmp_before = *icmp;
// ICMP इको प्रतिक्रिया में फ्लैग बदलना
icmp->type = ICMP_ECHOREPLY;
// चेकसम को 0 से आरंभ करें
icmp->checksum = 0;
__s64 value = bpf_csum_diff((void *)&icmp_before, sizeof(icmp_before), (void *)icmp, sizeof(*icmp), 0);
if (value >= 0)
icmp->checksum = csum_fold(value);
// ICMP इको अनुरोध पैकेट को ड्रॉप करें
// return XDP_DROP;
return XDP_TX;
}
char _license[] SEC("license") = "MIT";
static __always_inline __u16 csum_fold(__u32 csum)
{
// उच्च 16 बिट के मान को निम्न 16 बिट में जोड़ना
csum = (csum & 0xffff) + (csum >> 16);
// एक बार फिर कैर्रीओवर को जोड़ना
csum = (csum & 0xffff) + (csum >> 16);
// 1 के पूरक को लेकर अंतिम चेकसम लौटाना
return (__u16)~csum;
}
csum_fold
फ़ंक्शन 32-बिट चेकसम को 16-बिट में संकुचित करने की प्रक्रिया करता है। bpf_csum_diff() का लौटाए गए मूल्य 32 बिट का होता है, और इसमें कैर्री हो सकता है। इसलिए, पहले उच्च 16 बिट के मान को निम्न 16 बिट में जोड़ना और फिर एक बार और कैर्री किया जाता है। अंत में 1 के पूरक (बिट उल्टा) को लेकर अंतिम चेकसम मान को प्राप्त किया जाता है।
static __always_inline void swap_eth_addr(__u8 *a, __u8 *b)
{
__u8 tmp[ETH_ALEN];
__builtin_memcpy(tmp, a, ETH_ALEN);
__builtin_memcpy(a, b, ETH_ALEN);
__builtin_memcpy(b, tmp, ETH_ALEN);
}
swap_eth_addr
फ़ंक्शन ईथरनेट हेडर के MAC पते को बदलने का कार्य करता है।
static __always_inline void swap_ip_addr(__u32 *a, __u32 *b)
{
__u32 tmp = *a;
*a = *b;
*b = tmp;
}
swap_ip_addr
फ़ंक्शन IP हेडर के IP पते को बदलने का कार्य करता है।
eBPF में, फ़ंक्शन कॉल को इनलाइन करने की आवश्यकता होती है, इसलिए __always_inline जोड़ा गया है।
// स्रोत और लक्ष्य के MAC पते को स्वैप करना
swap_eth_addr(eth->h_dest, eth->h_source);
// स्रोत और लक्ष्य के IP पते को स्वैप करना
swap_ip_addr(&ip->saddr, &ip->daddr);
// ICMP हेडर की प्रतिलिपि बनाएँ
struct icmphdr icmp_before = *icmp;
// ICMP इको प्रतिक्रिया के लिए फ्लैग को बदलें
icmp->type = ICMP_ECHOREPLY;
// चेकसम को 0 से आरंभ करें
icmp->checksum = 0;
__s64 value = bpf_csum_diff((void *)&icmp_before, sizeof(icmp_before), (void *)icmp, sizeof(*icmp), 0);
if (value >= 0)
icmp->checksum = csum_fold(value);
यह भाग पहले से भिन्न है।swap_eth_addr
और swap_ip_addr
में MAC पते और IP पते को बदलने के कार्य को फ़ंक्शन में बदल दिया गया है।
icmp
की प्रतिलिपि बनाई गई, और icmp->type
को ICMP_ECHOREPLY
में बदला गया।
इसके बाद चेकसम की गणना की जा रही है।
icmp->checksum = 0; के साथ चेकसम को 0 से आरंभ करें और फिर bpf_csum_diff() से अंतर की गणना करें। इसके बाद, bpf_csum_diff का लौटाया गया मान कैर्री नहीं करेगा, इसलिए csum_fold() के माध्यम से संकुचित किया जाता है।
कार्य की पुष्टि
क्लाइंट से पिंग भेजें
$ ping 192.168.XXX.XXX
क्लाइंट द्वारा भेजे गए पिंग पर ICMP इको प्रतिक्रिया की पुष्टि करें।
$ sudo tcpdump -i eth0 icmp -vvv XX:XX:XX.413478 IP (tos 0x0, ttl 64, id 35523, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.XXX.1 > 192.168.XXX.2: ICMP echo request, id 64346, seq 16, length 64 XX:XX:XX.414359 IP (tos 0x0, ttl 64, id 35523, offset 0, flags [DF], proto ICMP (1), length 84) 192.168.XXX.2 > 192.168.XXX.1: ICMP echo reply, id 64346, seq 16, length 64
जैसे ही चेकसम की त्रुटि हल हुई, ICMP इको प्रतिक्रिया वापस आ रही है।
वास्तव में …
वास्तव में, IP हेडर में भी चेकसम होता है, इसलिए वास्तव में इसे पुनः गणना करने की आवश्यकता है।
- IP हेडर संरचना
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | संस्करण | IHL | DSCP | ECN | कुल लंबाई | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | पहचान |फ्लैग| फ्रैगमेंट ऑफसेट | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | जीवित रहने का समय | प्रोटोकॉल | हेडर चेकसम | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | स्रोत IP पता | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | लक्ष्य IP पता | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | विकल्प (यदि कोई हो) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
हैडर चेकसम फ़ील्ड होता है, और वास्तव में इसकी गणना दोबारा करनी चाहिए।
हालांकि, इस बार स्रोत और लक्ष्य IP को 16-बिट यूनिट में सीधे स्वैप किया गया है, इसलिए चेकसम की गणना के तरीके के कारण परिणामस्वरूप चेकसम बदला नहीं जाता है।
यह इस विशेषता के कारण है कि “यदि IP पते को 16-बिट यूनिट में स्वैप किया जाता है, तो चेकसम के समग्र मान के 1 का पूरक नहीं बदलता है।” इसलिए, इस बार यह केवल संयोग से था कि इसमें कोई प्रभाव नहीं पड़ा, और यदि अन्य फ़ील्ड को बदल दिया जाता है तो IP चेकसम की गणना हमेशा करनी चाहिए।
आपकी आवश्यकता के अनुसार, IP हेडर को ICMP हेडर की तरह पुनः गणना करें और उपयोग करें।
IP हेडर के लिए चेकसम भी गणना करें
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <arpa/inet.h>
static __always_inline __u16 csum_fold(__u32 csum)
{
// उच्च 16 बिट के मान को निम्न 16 बिट में जोड़ना
csum = (csum & 0xffff) + (csum >> 16);
// एक बार फिर कैर्रीओवर को जोड़ना
csum = (csum & 0xffff) + (csum >> 16);
// 1 के पूरक को लेकर अंतिम चेकसम लौटाना
return (__u16)~csum;
}
static __always_inline void swap_eth_addr(__u8 *a, __u8 *b)
{
__u8 tmp[ETH_ALEN];
__builtin_memcpy(tmp, a, ETH_ALEN);
__builtin_memcpy(a, b, ETH_ALEN);
__builtin_memcpy(b, tmp, ETH_ALEN);
}
static __always_inline void swap_ip_addr(__u32 *a, __u32 *b)
{
__u32 tmp = *a;
*a = *b;
*b = tmp;
}
SEC("xdp")
int xdp_echo_reply(struct xdp_md *ctx)
{
void *data_end = (void *)(unsigned long)ctx->data_end;
void *data = (void *)(unsigned long)ctx->data;
// ईथरनेट हेडर का पॉइंटर प्राप्त करना
struct ethhdr *eth = data;
if ((void *)eth + sizeof(*eth) > data_end)
return XDP_PASS;
// ये देखना कि ईथरनेट फ्रेम में IP पैकेट मौजूद है
if (eth->h_proto != htons(ETH_P_IP))
return XDP_PASS;
// IP हेडर का पॉइंटर प्राप्त करना
struct iphdr *ip = data + sizeof(*eth);
if ((void *)ip + sizeof(*ip) > data_end)
return XDP_PASS;
// सुनिश्चित करना कि प्रोटोकॉल ICMP है
if (ip->protocol != IPPROTO_ICMP)
return XDP_PASS;
// ICMP हेडर का पॉइंटर प्राप्त करना
struct icmphdr *icmp = (void *)ip + sizeof(*ip);
if ((void *)icmp + sizeof(*icmp) > data_end)
return XDP_PASS;
// ICMP इको अनुरोध के अलावा अन्य पैकेट को पास करना
if (icmp->type != ICMP_ECHO)
return XDP_PASS;
// IP हेडर की प्रतिलिपि बनाएँ
struct iphdr ip_before = *ip;
// स्रोत और लक्ष्य MAC पते को स्वैप करना
swap_eth_addr(eth->h_dest, eth->h_source);
// स्रोत और लक्ष्य IP पते को स्वैप करना
swap_ip_addr(&ip->saddr, &ip->daddr);
// ICMP हेडर की प्रतिलिपि बनाएँ
struct icmphdr icmp_before = *icmp;
// ICMP इको प्रतिक्रिया के लिए फ्लैग को बदलें
icmp->type = ICMP_ECHOREPLY;
// चेकसम को 0 से आरंभ करें
icmp->checksum = 0;
__s64 value = bpf_csum_diff((void *)&icmp_before, sizeof(icmp_before), (void *)icmp, sizeof(*icmp), 0);
if (value >= 0)
icmp->checksum = csum_fold(value);
// IP हेडर चेकसम
ip->check = 0;
value = bpf_csum_diff((void *)&ip_before, sizeof(ip_before), (void *)ip, sizeof(*ip), 0);
if (value >= 0)
ip->check = csum_fold(value);
// ICMP इको अनुरोध पैकेट को ड्रॉप करें
// return XDP_DROP;
return XDP_TX;
}
char _license[] SEC("license") = "MIT";
swap_ip_addr
में IP हेडर को बदलने से पहले इसकी प्रतिलिपि बनानी चाहिए, और ICMP चेकसम की तरह ही bpf_csum_diff
के माध्यम से अंतर की गणना की जानी चाहिए। इससे IP हेडर के चेकसम भी सही ढंग से गणना होंगे।
सारांश
- XDP का उपयोग करके ICMP इको अनुरोध के जवाब में ICMP इको प्रतिक्रिया लौटाने के लिए XDP कार्यक्रम को लागू किया गया।
- ICMP के चेकसम की गणना और इसे सही ढंग से लौटाने के तरीके का परिचय दिया गया।
- चेकसम की गणना केवल 16-बिट यूनिट में संशोधन स्थलों के अंतर को लेकर की जाती है, सभी डेटा की पुनः गणना करने की आवश्यकता नहीं होती है।
bpf_csum_diff()
नामक फ़ंक्शन का उपयोग करके चेकसम की गणना करना आसान हो जाता है।