
eBPF प्रोग्राम्स के लिए Rust वर्चुअल मशीन और JIT कंपाइलर
eBPF के लिए Rust (user-space) वर्चुअल मशीन
इस crate में eBPF प्रोग्राम निष्पादन के लिए एक वर्चुअल मशीन है। BPF, अर्थात् Berkeley Packet Filter, एक assembly-जैसी भाषा है जो मूल रूप से BSD सिस्टम के लिए विकसित की गई थी, ताकि tcpdump जैसे उपकरणों के साथ kernel में पैकेटों को फ़िल्टर किया जा सके और user-space में अनावश्यक प्रतियों से बचा जा सके। इसे Linux में पोर्ट किया गया, जहाँ यह eBPF (extended BPF) में विकसित हुआ, जो अधिक सुविधाओं वाला एक तेज़ संस्करण है। जबकि BPF प्रोग्राम मूल रूप से kernel में चलाने के लिए बनाए गए हैं, इस crate की वर्चुअल मशीन इसे user-space अनुप्रयोगों में चलाने में सक्षम बनाती है; इसमें एक interpreter, eBPF प्रोग्रामों के लिए एक x86_64 JIT-compiler, तथा एक disassembler शामिल है।
यह Rich Lane के uBPF सॉफ़्टवेयर पर आधारित है, जो लगभग वही कार्य करता है, लेकिन C में लिखा गया है।
यह crate Linux, MacOS X, और Windows पर संकलित और चलने योग्य माना जाता है, हालाँकि JIT-compiler इस समय Windows के साथ काम नहीं करता है।
यह crate crates.io से उपलब्ध है, इसलिए इसे अपनी Cargo.toml
फ़ाइल में एक dependency के रूप में जोड़कर यह बिना किसी अतिरिक्त प्रयास के काम करना चाहिए:
[dependencies]
rbpf = "0.4.1"
आप इस GitHub रिपॉज़िटरी से डेवलपमेंट वर्शन का भी उपयोग कर सकते हैं। यह
आपके Cargo.toml में इसे डालने जितना ही सरल होना चाहिए:
[dependencies]
rbpf = { git = "https://github.com/qmonnet/rbpf" }
बेशक, अगर आप चाहें, तो आप इसे लोकली क्लोन कर सकते हैं, संभवतः crate को हैक कर सकते हैं,
और फिर Cargo.toml में अपने लोकल वर्शन का पाथ इंडिकेट कर सकते हैं:
[dependencies]
rbpf = { path = "path/to/rbpf" }
फिर अपने स्रोत कोड में इंगित करें कि आप crate का उपयोग करना चाहते हैं:
extern crate rbpf;
API स्रोत कोड के अंदर काफी अच्छी तरह से प्रलेखित है। आपको यहाँ से दस्तावेज़ का एक ऑनलाइन संस्करण भी एक्सेस करने में सक्षम होना चाहिए, जो crates.io संस्करण से स्वचालित रूप से जनरेट किया गया है (मुख्य शाखा के साथ अद्यतित नहीं हो सकता है)। उदाहरण और यूनिट परीक्षण भी सहायक सिद्ध होंगे। यहाँ crate का उपयोग करने के तरीके का सारांश दिया गया है।
rbpf के साथ एक eBPF प्रोग्राम चलाने के लिए यहाँ अनुसरण करने के चरण दिए गए हैं:
eBPF को शुरू में पैकेट फ़िल्टर करने के लिए डिज़ाइन किया गया था (अब Linux कर्नेल में इसके कुछ अन्य हुक हैं, जैसे kprobes, लेकिन यह rbpf द्वारा कवर नहीं किया गया है)। परिणामस्वरूप, प्रोग्राम के अधिकांश लोड और स्टोर निर्देश पैकेट डेटा का प्रतिनिधित्व करने वाले मेमोरी क्षेत्र पर किए जाते हैं। हालाँकि, Linux कर्नेल में, eBPF प्रोग्राम इस डेटा क्षेत्र को तुरंत एक्सेस नहीं करता है: शुरू में, इसके पास एक C struct sk_buff तक पहुँच होती है, जो पैकेट के बारे में मेटाडेटा वाला एक बफ़र है—जिसमें पैकेट डेटा क्षेत्र की शुरुआत और अंत के मेमोरी पते शामिल हैं। इसलिए प्रोग्राम पहले उन पॉइंटर्स को sk_buff से लोड करता है, और फिर पैकेट डेटा को एक्सेस कर सकता है।
इस व्यवहार को rbpf के साथ दोहराया जा सकता है, लेकिन यह अनिवार्य नहीं है। इस कारण से, हमारे पास विभिन्न प्रकार की वर्चुअल मशीनों का प्रतिनिधित्व करने वाले कई structs हैं:
struct EbpfVmMbuffer कर्नेल की नकल करता है। जब प्रोग्राम चलाया जाता है, तो इसके पहले eBPF रजिस्टर को दिया गया पता उपयोगकर्ता द्वारा प्रदान किए गए मेटाडेटा बफ़र का पता होगा, और उससे पैकेट डेटा मेमोरी क्षेत्र की शुरुआत और अंत के पॉइंटर्स होने की अपेक्षा की जाती है।
struct EbpfVmFixedMbuff का एक उद्देश्य है: कर्नेल के साथ संगत होने के लिए बनाए गए प्रोग्रामों के निष्पादन को सक्षम करना, जबकि उपयोगकर्ता के लिए मेटाडेटा बफ़र को मैन्युअल रूप से संभालने के प्रयास को बचाना। वास्तव में, इस struct में एक स्थिर आंतरिक बफ़र है जो प्रोग्राम को पास किया जाता है। उपयोगकर्ता को उन ऑफ़सेट मानों को इंगित करना होता है जिन पर eBPF प्रोग्राम बफ़र में पैकेट डेटा की शुरुआत और अंत को खोजने की अपेक्षा करता है। प्रोग्राम चलाने वाले फ़ंक्शन को कॉल करने पर (JITted हो या नहीं), struct स्वचालित रूप से इस स्थिर बफ़र में पतों को, निर्दिष्ट ऑफ़सेट पर, पैकेट डेटा की शुरुआत और अंत के लिए अपडेट करता है जिस पर प्रोग्राम को कॉल किया जाता है।
struct EbpfVmRaw उन प्रोग्रामों के लिए है जो सीधे पैकेट डेटा पर चलना चाहते हैं। कोई मेटाडेटा बफ़र शामिल नहीं है, eBPF प्रोग्राम सीधे अपने पहले रजिस्टर में पैकेट डेटा का पता प्राप्त करता है। यह uBPF का व्यवहार है।
struct EbpfVmNoData कोई डेटा नहीं लेता है। eBPF प्रोग्राम कोई तर्क नहीं लेता है और इसका रिटर्न मान नियतात्मक होता है। निश्चित नहीं कि इसके लिए कोई वैध उपयोग मामला है, लेकिन अगर और कुछ नहीं, तो यह यूनिट परीक्षणों के लिए बहुत उपयोगी है।
ये सभी structs समान सार्वजनिक फ़ंक्शन लागू करते हैं:
// called with EbpfVmMbuff:: prefix
pub fn new(prog: &'a [u8]) -> Result<EbpfVmMbuff<'a>, Error>
// called with EbpfVmFixedMbuff:: prefix
pub fn new(prog: &'a [u8],
data_offset: usize,
data_end_offset: usize) -> Result<EbpfVmFixedMbuff<'a>, Error>
// called with EbpfVmRaw:: prefix
pub fn new(prog: &'a [u8]) -> Result<EbpfVmRaw<'a>, Error>
// called with EbpfVmNoData:: prefix
pub fn new(prog: &'a [u8]) -> Result<EbpfVmNoData<'a>, Error>
इसका उपयोग VM का नया इंस्टेंस बनाने के लिए किया जाता है। रिटर्न प्रकार उस स्ट्रक्चर पर निर्भर करता है जिससे फ़ंक्शन को कॉल किया जाता है। उदाहरण के लिए,
rbpf::EbpfVmRaw::new(Some(my_program)) struct rbpf::EbpfVmRaw का एक इंस्टेंस लौटाएगा (एक Result में लिपटा हुआ)। जब कोई प्रोग्राम लोड होता है, तो इसे एक बहुत ही सरल वेरिफायर से जाँचा जाता है (Linux
kernel वाले के करीब भी नहीं)। उपयोगकर्ता इसे एक कस्टम वेरिफायर से भी बदल सकते हैं।
struct EbpfVmFixedMbuff के लिए, कंस्ट्रक्टर को दो अतिरिक्त आर्ग्युमेंट्स पास करने होते हैं: data_offset और data_end_offset। ये वे ऑफ़सेट (बाइट
संख्या) हैं जिन पर पैकेट डेटा के मेमोरी क्षेत्र के आरंभ और अंत के पॉइंटर्स को, क्रमशः, हर बार प्रोग्राम चलाए जाने पर आंतरिक मेटाडेटा बफ़र में संग्रहीत किया जाना है। अन्य स्ट्रक्चर इस तंत्र का उपयोग नहीं करते और उन्हें उन ऑफ़सेट्स की आवश्यकता नहीं होती।
// for struct EbpfVmMbuff, struct EbpfVmRaw and struct EbpfVmRawData
pub fn set_program(&mut self, prog: &'a [u8]) -> Result<(), Error>
// for struct EbpfVmFixedMbuff
pub fn set_program(&mut self, prog: &'a [u8],
data_offset: usize,
data_end_offset: usize) -> Result<(), Error>