सानाहरूका लागि BPF, भाग शून्य: क्लासिक BPF

बर्कले प्याकेट फिल्टरहरू (BPF) एक लिनक्स कर्नेल टेक्नोलोजी हो जुन धेरै वर्षदेखि अंग्रेजी भाषाको प्राविधिक प्रकाशनहरूको पहिलो पृष्ठमा रहेको छ। सम्मेलनहरू BPF को उपयोग र विकासमा रिपोर्टहरूले भरिएका छन्। डेभिड मिलर, लिनक्स सञ्जाल सबसिस्टम मर्मतकर्ता, लिनक्स प्लम्बर्स 2018 मा आफ्नो कुराकानी बोलाउँछन् "यो कुराकानी XDP को बारेमा होइन" (XDP BPF को लागी एक प्रयोग केस हो)। ब्रेन्डन ग्रेगले हकदार भाषण दिन्छ लिनक्स BPF सुपरपावरहरू। Toke Høiland-Jørgensen हाँस्छकि कर्नेल अब माइक्रोकर्नेल हो। थोमस ग्राफले यो विचारलाई बढावा दिन्छ BPF कर्नेलको लागि जाभास्क्रिप्ट हो.

Habré मा BPF को अझै व्यवस्थित विवरण छैन, र त्यसैले लेखहरूको एक श्रृंखलामा म टेक्नोलोजीको इतिहासको बारेमा कुरा गर्ने प्रयास गर्नेछु, वास्तुकला र विकास उपकरणहरू वर्णन गर्नेछु, र BPF प्रयोग गर्ने अनुप्रयोग र अभ्यासको क्षेत्रहरू रूपरेखा गर्नेछु। यो लेख, शून्य, शृङ्खलामा, क्लासिक BPF को इतिहास र वास्तुकला बताउँछ, र यसको सञ्चालन सिद्धान्तहरूको रहस्य पनि प्रकट गर्दछ। tcpdump, seccomp, strace, र धेरै धेरै।

BPF को विकास लिनक्स नेटवर्किंग समुदाय द्वारा नियन्त्रित छ, BPF को मुख्य अवस्थित अनुप्रयोगहरू नेटवर्क संग सम्बन्धित छन् र त्यसैले, अनुमति संग @eucariot, मैले शृङ्खलालाई "सानाहरूका लागि BPF" भनिन्छ, महान श्रृंखलाको सम्मानमा "सानाहरूका लागि नेटवर्कहरू".

BPF को इतिहास मा एक छोटो पाठ्यक्रम (c)

आधुनिक BPF प्रविधि पुरानो प्रविधिको समान नामको सुधारिएको र विस्तारित संस्करण हो, जसलाई अब क्लासिक BPF भनिन्छ भ्रमबाट बच्न। क्लासिक BPF मा आधारित एक प्रसिद्ध उपयोगिता सिर्जना गरिएको थियो tcpdump, संयन्त्र seccomp, साथै कम ज्ञात मोड्युलहरू xt_bpf लागि iptables र वर्गीकरणकर्ता cls_bpf। आधुनिक लिनक्समा, क्लासिक BPF प्रोग्रामहरू स्वचालित रूपमा नयाँ फारममा अनुवाद हुन्छन्, तथापि, प्रयोगकर्ताको दृष्टिकोणबाट, API स्थानमा रहेको छ र क्लासिक BPF को लागि नयाँ प्रयोगहरू, हामी यस लेखमा देख्नेछौं, अझै फेला परेका छन्। यस कारणको लागि, र लिनक्समा शास्त्रीय BPF को विकासको इतिहास पछ्याउँदै, यो कसरी र किन यसको आधुनिक रूपमा विकसित भयो भन्ने कुरा स्पष्ट हुनेछ, मैले शास्त्रीय BPF को बारेमा लेखको साथ सुरू गर्ने निर्णय गरें।

गत शताब्दीको अस्सीको दशकको अन्त्यमा, प्रसिद्ध लरेन्स बर्कले प्रयोगशालाका इन्जिनियरहरूले गत शताब्दीको अस्सीको दशकको अन्ततिर आधुनिक भएको हार्डवेयरमा नेटवर्क प्याकेटहरू कसरी ठीकसँग फिल्टर गर्ने भन्ने प्रश्नमा चासो देखाए। फिल्टरिङको आधारभूत विचार, मूल रूपमा CSPF (CMU/Stanford Packet Filter) प्रविधिमा लागू गरिएको थियो, अनावश्यक प्याकेटहरू सकेसम्म चाँडो फिल्टर गर्नु थियो, अर्थात्। कर्नेल स्पेसमा, किनकि यसले प्रयोगकर्ता स्पेसमा अनावश्यक डाटा प्रतिलिपि गर्नबाट जोगाउँछ। कर्नेल स्पेसमा प्रयोगकर्ता कोड चलाउनको लागि रनटाइम सुरक्षा प्रदान गर्न, स्यान्डबक्स गरिएको भर्चुअल मेसिन प्रयोग गरिएको थियो।

यद्यपि, अवस्थित फिल्टरहरूका लागि भर्चुअल मेसिनहरू स्ट्याक-आधारित मेसिनहरूमा चलाउन डिजाइन गरिएको थियो र नयाँ RISC मेसिनहरूमा प्रभावकारी रूपमा चलेको थिएन। नतिजाको रूपमा, बर्कले ल्याब्सका इन्जिनियरहरूको प्रयासबाट, नयाँ BPF (बर्कले प्याकेट फिल्टरहरू) प्रविधि विकसित भएको थियो, जसको भर्चुअल मेसिन आर्किटेक्चर मोटोरोला 6502 प्रोसेसरमा आधारित डिजाइन गरिएको थियो - त्यस्ता प्रसिद्ध उत्पादनहरूको वर्कहोर्स। Apple II वा NES। नयाँ भर्चुअल मेसिनले अवस्थित समाधानहरूको तुलनामा फिल्टर कार्यसम्पादन दशौं पटक बढायो।

BPF मेसिन वास्तुकला

हामी काम गर्ने तरिकामा वास्तुकलासँग परिचित हुनेछौं, उदाहरणहरूको विश्लेषण गर्दै। यद्यपि, सुरु गर्नको लागि, मानौं कि मेसिनमा प्रयोगकर्ताको लागि पहुँचयोग्य दुई 32-बिट रेजिस्टरहरू थिए, एक संचयक। A र सूचकांक दर्ता X, 64 बाइट मेमोरी (16 शब्दहरू), लेख्न र पछि पढ्नको लागि उपलब्ध छ, र यी वस्तुहरूसँग काम गर्न आदेशहरूको सानो प्रणाली। सशर्त अभिव्यक्तिहरू लागू गर्नका लागि जम्प निर्देशनहरू कार्यक्रमहरूमा पनि उपलब्ध थिए, तर कार्यक्रमको समयमै समाप्तिको ग्यारेन्टी गर्न, जम्पहरू मात्र अगाडि बढाउन सकिन्छ, अर्थात्, विशेष गरी, यो लूपहरू सिर्जना गर्न निषेध गरिएको थियो।

मेसिन सुरु गर्ने सामान्य योजना निम्नानुसार छ। प्रयोगकर्ताले BPF वास्तुकलाको लागि एक कार्यक्रम सिर्जना गर्दछ र, प्रयोग गरेर केही कर्नेल मेकानिजम (जस्तै प्रणाली कल), कार्यक्रमलाई लोड र जडान गर्दछ केहीलाई कर्नेलमा घटना जनरेटरमा (उदाहरणका लागि, घटना भनेको नेटवर्क कार्डमा अर्को प्याकेटको आगमन हो)। जब घटना हुन्छ, कर्नेलले कार्यक्रम चलाउँछ (उदाहरणका लागि, अनुवादकमा), र मेशिन मेमोरीसँग मेल खान्छ। केहीलाई कर्नेल मेमोरी क्षेत्र (उदाहरणका लागि, आगमन प्याकेटको डाटा)।

माथिको हाम्रो लागि उदाहरणहरू हेर्न सुरु गर्न पर्याप्त हुनेछ: हामी आवश्यक रूपमा प्रणाली र आदेश ढाँचासँग परिचित हुनेछौं। यदि तपाइँ तुरुन्तै भर्चुअल मेसिनको आदेश प्रणाली अध्ययन गर्न र यसको सबै क्षमताहरू बारे जान्न चाहनुहुन्छ भने, तपाइँ मूल लेख पढ्न सक्नुहुन्छ। BSD प्याकेट फिल्टर र/वा फाइलको पहिलो आधा Documentation/networking/filter.txt कर्नेल कागजातबाट। साथै, तपाईं प्रस्तुति अध्ययन गर्न सक्नुहुन्छ libpcap: प्याकेट क्याप्चरको लागि एक वास्तुकला र अनुकूलन विधि, जसमा BPF को लेखकहरू मध्ये एक McCanne ले सृष्टिको इतिहासको बारेमा कुरा गर्छ libpcap.

हामी अब लिनक्समा क्लासिक BPF प्रयोग गर्ने सबै महत्त्वपूर्ण उदाहरणहरू विचार गर्न अगाडि बढ्छौं: tcpdump (libpcap), seccomp, xt_bpf, cls_bpf.

tcpdump

BPF को विकास प्याकेट फिल्टरिंग को लागी फ्रन्टएन्ड को विकास संग समानांतर मा गरिएको थियो - एक प्रसिद्ध उपयोगिता tcpdump। र, धेरै अपरेटिङ सिस्टमहरूमा उपलब्ध क्लासिक BPF प्रयोग गर्ने यो सबैभन्दा पुरानो र सबैभन्दा प्रसिद्ध उदाहरण भएकोले, हामी यससँग प्रविधिको हाम्रो अध्ययन सुरु गर्नेछौं।

(मैले लिनक्समा यस लेखमा सबै उदाहरणहरू चलाएँ 5.6.0-rc6। केही आदेशहरूको आउटपुट राम्रो पठनीयताको लागि सम्पादन गरिएको छ।)

उदाहरण: IPv6 प्याकेटहरू अवलोकन गर्दै

कल्पना गरौं कि हामी इन्टरफेसमा सबै IPv6 प्याकेटहरू हेर्न चाहन्छौं eth0। यसका लागि हामी कार्यक्रम चलाउन सक्छौं tcpdump एक साधारण फिल्टर संग ip6:

$ sudo tcpdump -i eth0 ip6

यसरी tcpdump फिल्टर कम्पाइल गर्दछ ip6 BPF आर्किटेक्चर बाइटकोडमा र यसलाई कर्नेलमा पठाउनुहोस् (खण्डमा विवरणहरू हेर्नुहोस् Tcpdump: लोड गर्दै)। लोड गरिएको फिल्टर इन्टरफेस मार्फत जाने प्रत्येक प्याकेटको लागि चलाइनेछ eth0। यदि फिल्टरले गैर-शून्य मान फर्काउँछ n, त्यसपछि सम्म n प्याकेटको बाइटहरू प्रयोगकर्ता स्पेसमा प्रतिलिपि गरिनेछ र हामी यसलाई आउटपुटमा देख्नेछौं tcpdump.

सानाहरूका लागि BPF, भाग शून्य: क्लासिक BPF

यो बाहिर जान्छ कि हामी सजिलै पत्ता लगाउन सक्छौं कि कुन बाइटकोड कर्नेलमा पठाइएको थियो tcpdump को सहयोगमा tcpdump, यदि हामी यसलाई विकल्प संग चलाउँछौं -d:

$ sudo tcpdump -i eth0 -d ip6
(000) ldh      [12]
(001) jeq      #0x86dd          jt 2    jf 3
(002) ret      #262144
(003) ret      #0

लाइन शून्यमा हामी आदेश चलाउँछौं ldh [12], जसको अर्थ "लोड इन रेजिस्टर" हो A आधा शब्द (१६ बिट्स) ठेगाना १२ मा अवस्थित छ" र एउटै प्रश्न भनेको हामी कस्तो प्रकारको मेमोरीलाई सम्बोधन गर्दैछौं? जवाफ छ कि मा x सुरु हुन्छ (x+1)विश्लेषण गरिएको नेटवर्क प्याकेटको औं बाइट। हामी इथरनेट इन्टरफेसबाट प्याकेटहरू पढ्छौं eth0र यो यसको अर्थकि प्याकेट यस्तो देखिन्छ (सरलताको लागि, हामी मान्दछौं कि प्याकेटमा कुनै VLAN ट्यागहरू छैनन्):

       6              6          2
|Destination MAC|Source MAC|Ether Type|...|

त्यसैले आदेश कार्यान्वयन पछि ldh [12] दर्ता मा A त्यहाँ एक क्षेत्र हुनेछ Ether Type — यो इथरनेट फ्रेममा प्रसारित प्याकेटको प्रकार। लाइन १ मा हामी दर्ताको सामग्री तुलना गर्छौं A (प्याकेज प्रकार) ग 0x86ddर यो र छ हामीले रुचि राखेको प्रकार IPv6 हो। लाइन 1 मा, तुलना आदेश को अतिरिक्त, त्यहाँ दुई थप स्तम्भहरू छन् - jt 2 и jf 3 - तुलना सफल भएमा तपाईले जान आवश्यक पर्ने अंकहरू (A == 0x86dd) र असफल। त्यसोभए, सफल केस (IPv6) मा हामी लाइन 2 मा जान्छौं, र असफल अवस्थामा - लाइन 3 मा। लाइन 3 मा प्रोग्राम कोड 0 को साथ समाप्त हुन्छ (प्याकेट प्रतिलिपि नगर्नुहोस्), लाइन 2 मा प्रोग्राम कोडको साथ समाप्त हुन्छ। 262144 (मलाई अधिकतम 256 किलोबाइट प्याकेज प्रतिलिपि गर्नुहोस्)।

थप जटिल उदाहरण: हामी गन्तव्य पोर्ट द्वारा TCP प्याकेटहरू हेर्छौं

गन्तव्य पोर्ट 666 सँग सबै TCP प्याकेटहरू प्रतिलिपि गर्ने फिल्टरले कस्तो देखिन्छ हेरौं। हामी IPv4 केसलाई विचार गर्नेछौं, किनकि IPv6 केस सरल छ। यो उदाहरण अध्ययन गरेपछि, तपाइँ आफैलाई अभ्यासको रूपमा IPv6 फिल्टर अन्वेषण गर्न सक्नुहुन्छ (ip6 and tcp dst port 666) र सामान्य केसको लागि फिल्टर (tcp dst port 666)। त्यसोभए, हामीले चासो राखेको फिल्टर यस्तो देखिन्छ:

$ sudo tcpdump -i eth0 -d ip and tcp dst port 666
(000) ldh      [12]
(001) jeq      #0x800           jt 2    jf 10
(002) ldb      [23]
(003) jeq      #0x6             jt 4    jf 10
(004) ldh      [20]
(005) jset     #0x1fff          jt 10   jf 6
(006) ldxb     4*([14]&0xf)
(007) ldh      [x + 16]
(008) jeq      #0x29a           jt 9    jf 10
(009) ret      #262144
(010) ret      #0

हामीलाई पहिले नै थाहा छ कि रेखाहरू 0 र 1 ले के गर्छ। लाइन 2 मा हामीले पहिले नै जाँच गरिसकेका छौं कि यो IPv4 प्याकेट हो (ईथर प्रकार = 0x800) र यसलाई दर्तामा लोड गर्नुहोस् A प्याकेटको 24 औं बाइट। हाम्रो प्याकेज जस्तो देखिन्छ

       14            8      1     1
|ethernet header|ip fields|ttl|protocol|...|

जसको मतलब हामी दर्तामा लोड गर्छौं A IP हेडरको प्रोटोकल फिल्ड, जुन तार्किक छ, किनभने हामी TCP प्याकेटहरू मात्र प्रतिलिपि गर्न चाहन्छौं। हामी प्रोटोकलसँग तुलना गर्छौं 0x6 (IPPROTO_TCPलाइन 3 मा।

लाइन 4 र 5 मा हामी ठेगाना 20 मा स्थित हाफवर्डहरू लोड गर्छौं र आदेश प्रयोग गर्दछौं jset जाँच गर्नुहोस् कि तीन मध्ये एक सेट गरिएको छ झण्डाहरू - जारी गरिएको मास्क लगाएर jset तीन सबैभन्दा महत्त्वपूर्ण बिटहरू खाली छन्। तीनवटा बिट्स मध्ये दुईले हामीलाई बताउँछ कि प्याकेट टुक्रा गरिएको IP प्याकेटको अंश हो, र यदि त्यसो हो भने, यो अन्तिम टुक्रा हो कि होइन। तेस्रो बिट आरक्षित छ र शून्य हुनुपर्छ। हामी या त अपूर्ण वा टुटेको प्याकेटहरू जाँच गर्न चाहँदैनौं, त्यसैले हामी सबै तीन बिटहरू जाँच गर्छौं।

रेखा 6 यस सूचीमा सबैभन्दा रोचक छ। अभिव्यक्ति ldxb 4*([14]&0xf) यसको मतलब हामी दर्तामा लोड गर्छौं X प्याकेटको पन्ध्रौं बाइटको सबैभन्दा कम महत्त्वपूर्ण चार बिटहरू 4 द्वारा गुणा गरियो। पन्ध्रौं बाइटको सबैभन्दा कम महत्त्वपूर्ण चार बिट फिल्ड हो इन्टरनेट हेडर लम्बाइ IPv4 हेडर, जसले शब्दहरूमा हेडरको लम्बाइ भण्डार गर्दछ, त्यसैले तपाईंले त्यसपछि 4 ले गुणन गर्न आवश्यक छ। चाखलाग्दो कुरा, अभिव्यक्ति 4*([14]&0xf) एक विशेष ठेगाना योजना को लागी एक पदनाम हो जुन यो फारम मा र केवल एक दर्ता को लागी मात्र प्रयोग गर्न सकिन्छ X, अर्थात् हामी पनि भन्न सक्दैनौं ldb 4*([14]&0xf) न त ldxb 5*([14]&0xf) (हामी मात्र फरक अफसेट निर्दिष्ट गर्न सक्छौं, उदाहरणका लागि, ldxb 4*([16]&0xf))। यो स्पष्ट छ कि यो ठेगाना योजना BPF मा ठ्याक्कै प्राप्त गर्न थपिएको थियो X (सूचकांक दर्ता) IPv4 हेडर लम्बाइ।

त्यसैले लाइन 7 मा हामी आधा शब्द लोड गर्ने प्रयास गर्छौं (X+16)। याद गर्दै कि 14 बाइटहरू इथरनेट हेडर द्वारा कब्जा गरिएको छ, र X IPv4 हेडरको लम्बाइ समावेश गर्दछ, हामी बुझ्दछौं कि मा A TCP गन्तव्य पोर्ट लोड गरिएको छ:

       14           X           2             2
|ethernet header|ip header|source port|destination port|

अन्तमा, लाइन 8 मा हामीले गन्तव्य पोर्टलाई इच्छित मानसँग तुलना गर्छौं र 9 वा 10 लाइनहरूमा हामी परिणाम फर्काउँछौं - चाहे प्याकेट प्रतिलिपि गर्ने वा नगर्ने।

Tcpdump: लोड गर्दै

अघिल्लो उदाहरणहरूमा, हामीले विशेष रूपमा प्याकेट फिल्टरिङको लागि कर्नेलमा BPF बाइटकोड कसरी लोड गर्छौं भन्ने बारे विस्तृत रूपमा ध्यान दिएनौं। सामान्यतया, tcpdump धेरै प्रणालीहरूमा पोर्ट गरिएको छ र फिल्टरहरूसँग काम गर्नका लागि tcpdump पुस्तकालय प्रयोग गर्दछ libpcap। छोटकरीमा, प्रयोग गरी इन्टरफेसमा फिल्टर राख्न libpcap, तपाईंलाई निम्नलिखित गर्न आवश्यक छ:

  • एक प्रकार वर्णनकर्ता सिर्जना गर्नुहोस् pcap_t इन्टरफेस नामबाट: pcap_create,
  • इन्टरफेस सक्रिय गर्नुहोस्: pcap_activate,
  • कम्पाइल फिल्टर: pcap_compile,
  • जडान फिल्टर: pcap_setfilter.

कसरी कार्य गर्ने हेर्न pcap_setfilter लिनक्समा लागू, हामी प्रयोग गर्छौं strace (केही रेखाहरू हटाइएका छन्):

$ sudo strace -f -e trace=%network tcpdump -p -i eth0 ip
socket(AF_PACKET, SOCK_RAW, 768)        = 3
bind(3, {sa_family=AF_PACKET, sll_protocol=htons(ETH_P_ALL), sll_ifindex=if_nametoindex("eth0"), sll_hatype=ARPHRD_NETROM, sll_pkttype=PACKET_HOST, sll_halen=0}, 20) = 0
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=4, filter=0xb00bb00bb00b}, 16) = 0
...

आउटपुटको पहिलो दुई लाइनहरूमा हामीले सिर्जना गर्छौं कच्चा सकेट सबै इथरनेट फ्रेमहरू पढ्न र यसलाई इन्टरफेसमा बाँध्न eth0... को हाम्रो पहिलो उदाहरण हामीलाई थाहा छ कि फिल्टर ip चार BPF निर्देशनहरू समावेश हुनेछ, र तेस्रो लाइनमा हामी कसरी विकल्प प्रयोग गर्ने देख्छौं SO_ATTACH_FILTER प्रणाली कल setsockopt हामी लम्बाइ 4 को फिल्टर लोड र जडान गर्छौं। यो हाम्रो फिल्टर हो।

यो ध्यान दिन लायक छ कि क्लासिक BPF मा, फिल्टर लोड र जडान सधैं एक परमाणु अपरेशन को रूप मा हुन्छ, र BPF को नयाँ संस्करण मा, कार्यक्रम लोड र घटना जेनरेटर मा बाइन्डिंग समय मा अलग छ।

लुकेको सत्य

आउटपुटको अलि बढी पूर्ण संस्करण यस्तो देखिन्छ:

$ sudo strace -f -e trace=%network tcpdump -p -i eth0 ip
socket(AF_PACKET, SOCK_RAW, 768)        = 3
bind(3, {sa_family=AF_PACKET, sll_protocol=htons(ETH_P_ALL), sll_ifindex=if_nametoindex("eth0"), sll_hatype=ARPHRD_NETROM, sll_pkttype=PACKET_HOST, sll_halen=0}, 20) = 0
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=1, filter=0xbeefbeefbeef}, 16) = 0
recvfrom(3, 0x7ffcad394257, 1, MSG_TRUNC, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable)
setsockopt(3, SOL_SOCKET, SO_ATTACH_FILTER, {len=4, filter=0xb00bb00bb00b}, 16) = 0
...

माथि उल्लेख गरिए अनुसार, हामी लाइन 5 मा सकेटमा हाम्रो फिल्टर लोड र जडान गर्छौं, तर लाइन 3 र 4 मा के हुन्छ? यो बाहिर जान्छ कि यो libpcap हाम्रो ख्याल राख्छ - ताकि हाम्रो फिल्टरको आउटपुटले यसलाई सन्तुष्ट नगर्ने प्याकेटहरू समावेश गर्दैन, पुस्तकालय जडान गर्दछ डमी फिल्टर ret #0 (सबै प्याकेटहरू छोड्नुहोस्), सकेटलाई नन-ब्लकिङ मोडमा स्विच गर्छ र अघिल्लो फिल्टरहरूबाट रहन सक्ने सबै प्याकेटहरू घटाउने प्रयास गर्छ।

कुल मिलाएर, क्लासिक BPF प्रयोग गरेर लिनक्समा प्याकेजहरू फिल्टर गर्न, तपाइँसँग संरचनाको रूपमा फिल्टर हुनु आवश्यक छ। struct sock_fprog र खुला सकेट, जस पछि फिल्टरलाई प्रणाली कल प्रयोग गरेर सकेटमा जोड्न सकिन्छ setsockopt.

चाखलाग्दो कुरा के छ भने, फिल्टर कुनै पनि सकेटमा संलग्न गर्न सकिन्छ, कच्चा मात्र होइन। यहाँ एक उदाहरण एउटा कार्यक्रम जसले सबै आगमन UDP डाटाग्रामबाट पहिलो दुई बाइटहरू बाहेक सबै काट्छ। (लेख अव्यवस्थित नहोस् भनेर मैले कोडमा टिप्पणीहरू थपें।)

प्रयोग बारे थप विवरण setsockopt फिल्टर जडानको लागि, हेर्नुहोस् सकेट(7), तर तपाईंको आफ्नै फिल्टरहरू लेख्ने बारे struct sock_fprog मद्दत बिना tcpdump हामी खण्डमा कुरा गर्नेछौं हाम्रो आफ्नै हातले BPF प्रोग्रामिङ.

क्लासिक BPF र XNUMX औं शताब्दी

BPF 1997 मा लिनक्स मा समावेश गरिएको थियो र लामो समय को लागी एक workhorse बनेको छ libpcap कुनै विशेष परिवर्तन बिना (लिनक्स-विशेष परिवर्तनहरू, अवश्य पनि, यो थियो, तर तिनीहरूले विश्वव्यापी चित्र परिवर्तन गरेनन्)। बीपीएफ विकसित हुने पहिलो गम्भीर संकेतहरू 2011 मा आए, जब एरिक डुमाजेटले प्रस्ताव गरे। प्याच, जसले कर्नेलमा Just In Time कम्पाइलर थप्छ - BPF bytecode लाई नेटिभमा रूपान्तरण गर्ने अनुवादक x86_64 कोड।

JIT कम्पाइलर परिवर्तनहरूको श्रृंखलामा पहिलो थियो: 2012 मा देखा पर्‍यो फिल्टरहरू लेख्न सक्ने क्षमता सेकेन्डबीपीएफ प्रयोग गरेर, जनवरी 2013 मा त्यहाँ थियो थपे मोड्युल xt_bpf, जसले तपाईंलाई नियमहरू लेख्न अनुमति दिन्छ iptables बीपीएफको सहयोगमा र अक्टोबर २०१३ मा भएको थियो थपे पनि एक मोड्युल cls_bpf, जसले तपाईंलाई BPF प्रयोग गरेर ट्राफिक वर्गीकरणहरू लेख्न अनुमति दिन्छ।

हामी यी सबै उदाहरणहरू चाँडै विस्तृत रूपमा हेर्नेछौं, तर पहिले यो हाम्रो लागि BPF को लागि स्वेच्छाचारी कार्यक्रमहरू कसरी लेख्ने र कम्पाइल गर्ने भनेर सिक्न उपयोगी हुनेछ, किनकि पुस्तकालयले प्रदान गरेको क्षमताहरू। libpcap सीमित (सरल उदाहरण: फिल्टर उत्पन्न libpcap केवल दुई मानहरू फर्काउन सक्छ - 0 वा 0x40000) वा सामान्यतया, seccomp को मामलामा, लागू हुँदैन।

हाम्रो आफ्नै हातले BPF प्रोग्रामिङ

BPF निर्देशनहरूको बाइनरी ढाँचासँग परिचित गरौं, यो धेरै सरल छ:

   16    8    8     32
| code | jt | jf |  k  |

प्रत्येक निर्देशनले 64 बिटहरू ओगटेको छ, जसमा पहिलो 16 बिटहरू निर्देशन कोड हुन्, त्यसपछि त्यहाँ दुई आठ-बिट इन्डेन्टहरू छन्, jt и jf, र तर्कको लागि 32 बिट K, जसको उद्देश्य आदेश अनुसार फरक हुन्छ। उदाहरणका लागि, आदेश ret, जुन कार्यक्रम समाप्त हुन्छ कोड छ 6, र फिर्ता मान स्थिर बाट लिइन्छ K। C मा, एकल BPF निर्देशन संरचनाको रूपमा प्रतिनिधित्व गरिएको छ

struct sock_filter {
        __u16   code;
        __u8    jt;
        __u8    jf;
        __u32   k;
}

र सम्पूर्ण कार्यक्रम संरचनाको रूपमा छ

struct sock_fprog {
        unsigned short len;
        struct sock_filter *filter;
}

यसरी, हामी पहिले नै प्रोग्रामहरू लेख्न सक्छौं (उदाहरणका लागि, हामीलाई निर्देशन कोडहरू थाहा छ [1])। यो फिल्टर जस्तो देखिने छ ip6 बाट हाम्रो पहिलो उदाहरण:

struct sock_filter code[] = {
        { 0x28, 0, 0, 0x0000000c },
        { 0x15, 0, 1, 0x000086dd },
        { 0x06, 0, 0, 0x00040000 },
        { 0x06, 0, 0, 0x00000000 },
};
struct sock_fprog prog = {
        .len = ARRAY_SIZE(code),
        .filter = code,
};

कार्यक्रम prog हामी कानुनी रूपमा कलमा प्रयोग गर्न सक्छौं

setsockopt(sk, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog))

मेसिन कोडको रूपमा प्रोग्रामहरू लेख्नु धेरै सुविधाजनक छैन, तर कहिलेकाहीँ यो आवश्यक हुन्छ (उदाहरणका लागि, डिबगिङको लागि, एकाइ परीक्षणहरू सिर्जना गर्न, Habré मा लेखहरू लेख्ने, आदि)। सुविधाको लागि, फाइलमा <linux/filter.h> सहायक म्याक्रोहरू परिभाषित छन् - माथिको जस्तै उदाहरण पुन: लेख्न सकिन्छ

struct sock_filter code[] = {
        BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 12),
        BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, ETH_P_IPV6, 0, 1),
        BPF_STMT(BPF_RET|BPF_K, 0x00040000),
        BPF_STMT(BPF_RET|BPF_K, 0),
}

यद्यपि, यो विकल्प धेरै सुविधाजनक छैन। यो के लिनक्स कर्नेल प्रोग्रामरहरूले तर्क गरे, र त्यसैले डाइरेक्टरीमा tools/bpf कर्नेलहरू तपाईंले क्लासिक BPF सँग काम गर्नको लागि एसेम्बलर र डिबगर फेला पार्न सक्नुहुन्छ।

विधानसभा भाषा डिबग आउटपुटसँग धेरै मिल्दोजुल्दो छ tcpdump, तर यसको अतिरिक्त हामी प्रतीकात्मक लेबलहरू निर्दिष्ट गर्न सक्छौं। उदाहरणका लागि, यहाँ एउटा कार्यक्रम छ जसले TCP/IPv4 बाहेक सबै प्याकेटहरू छोड्छ:

$ cat /tmp/tcp-over-ipv4.bpf
ldh [12]
jne #0x800, drop
ldb [23]
jneq #6, drop
ret #-1
drop: ret #0

पूर्वनिर्धारित रूपमा, एसेम्बलरले ढाँचामा कोड उत्पन्न गर्दछ <количество инструкций>,<code1> <jt1> <jf1> <k1>,..., TCP संग हाम्रो उदाहरण को लागी यो हुनेछ

$ tools/bpf/bpf_asm /tmp/tcp-over-ipv4.bpf
6,40 0 0 12,21 0 3 2048,48 0 0 23,21 0 1 6,6 0 0 4294967295,6 0 0 0,

C प्रोग्रामरहरूको सुविधाको लागि, फरक आउटपुट ढाँचा प्रयोग गर्न सकिन्छ:

$ tools/bpf/bpf_asm -c /tmp/tcp-over-ipv4.bpf
{ 0x28,  0,  0, 0x0000000c },
{ 0x15,  0,  3, 0x00000800 },
{ 0x30,  0,  0, 0x00000017 },
{ 0x15,  0,  1, 0x00000006 },
{ 0x06,  0,  0, 0xffffffff },
{ 0x06,  0,  0, 0000000000 },

यो पाठ प्रकार संरचना परिभाषा मा प्रतिलिपि गर्न सकिन्छ struct sock_filter, हामीले यस खण्डको सुरुमा गरे जस्तै।

लिनक्स र netsniff-ng विस्तारहरू

मानक BPF को अतिरिक्त, लिनक्स र tools/bpf/bpf_asm समर्थन र गैर-मानक सेट। सामान्यतया, निर्देशनहरू संरचनाको क्षेत्रहरूमा पहुँच गर्न प्रयोग गरिन्छ struct sk_buff, जसले कर्नेलमा नेटवर्क प्याकेट वर्णन गर्दछ। यद्यपि, त्यहाँ अन्य प्रकारका सहायक निर्देशनहरू पनि छन्, उदाहरणका लागि ldw cpu दर्तामा लोड हुनेछ A कर्नेल प्रकार्य चलाउने परिणाम raw_smp_processor_id()। (BPF को नयाँ संस्करणमा, यी गैर-मानक विस्तारहरू मेमोरी, संरचनाहरू, र घटनाहरू सिर्जना गर्न पहुँच गर्न कर्नेल सहयोगीहरूको सेटको साथ कार्यक्रमहरू प्रदान गर्न विस्तार गरिएको छ।) यहाँ एउटा फिल्टरको एउटा रोचक उदाहरण छ जसमा हामी केवल प्रतिलिपि गर्छौं। विस्तार प्रयोग गरेर प्रयोगकर्ता स्पेसमा प्याकेट हेडरहरू poff, पेलोड अफसेट:

ld poff
ret a

BPF विस्तारहरू प्रयोग गर्न सकिँदैन tcpdump, तर यो उपयोगिता प्याकेज संग परिचित हुन एक राम्रो कारण हो netsniff-ng, जसमा, अन्य चीजहरू बीच, एक उन्नत कार्यक्रम समावेश गर्दछ netsniff-ng, जसमा, BPF प्रयोग गरी फिल्टरिङको अतिरिक्त, प्रभावकारी ट्राफिक जेनेरेटर पनि समावेश छ, र यो भन्दा बढि उन्नत tools/bpf/bpf_asm, एक BPF एसेम्बलर बोलाइयो bpfc। प्याकेजमा धेरै विस्तृत कागजातहरू छन्, लेखको अन्त्यमा लिङ्कहरू पनि हेर्नुहोस्।

सेकेन्ड

त्यसोभए, हामीले मनमानी जटिलताका BPF कार्यक्रमहरू कसरी लेख्ने भनेर पहिले नै थाहा पाएका छौं र नयाँ उदाहरणहरू हेर्न तयार छौं, जसमध्ये पहिलो seccomp टेक्नोलोजी हो, जसले BPF फिल्टरहरू प्रयोग गरेर, उपलब्ध प्रणाली कल तर्कहरूको सेट र सेट व्यवस्थापन गर्न अनुमति दिन्छ। दिइएको प्रक्रिया र यसको सन्तान।

seccomp को पहिलो संस्करण 2005 मा कर्नेलमा थपिएको थियो र धेरै लोकप्रिय थिएन, किनकि यसले केवल एक विकल्प प्रदान गर्‍यो - प्रक्रियामा उपलब्ध प्रणाली कलहरूको सेटलाई निम्नमा सीमित गर्न: read, write, exit и sigreturn, र नियम उल्लंघन गर्ने प्रक्रिया प्रयोग गरी मारिएको थियो SIGKILL। यद्यपि, 2012 मा, seccomp ले BPF फिल्टरहरू प्रयोग गर्ने क्षमता थप्यो, तपाईंलाई अनुमति दिइएको प्रणाली कलहरूको सेट परिभाषित गर्न र तिनीहरूको तर्कहरूमा पनि जाँचहरू गर्न अनुमति दिँदै। (चाखलाग्दो कुरा के छ भने, क्रोम यस प्रकार्यको पहिलो प्रयोगकर्ताहरू मध्ये एक थियो, र क्रोमका मानिसहरूले हाल BPF को नयाँ संस्करणमा आधारित KRSI संयन्त्र विकास गर्दैछन् र लिनक्स सुरक्षा मोड्युलहरूको अनुकूलनलाई अनुमति दिन्छ।) थप कागजातहरूको लिङ्कहरू अन्तमा फेला पार्न सकिन्छ। लेख को।

ध्यान दिनुहोस् कि त्यहाँ पहिले नै seccomp प्रयोग गर्ने बारे हबमा लेखहरू छन्, हुनसक्छ कसैले तिनीहरूलाई निम्न उपखण्डहरू पढ्न अघि (वा सट्टा) पढ्न चाहन्छ। लेखमा कन्टेनर र सुरक्षा: seccomp Seccomp प्रयोग गर्ने उदाहरणहरू प्रदान गर्दछ, दुबै 2007 संस्करण र BPF प्रयोग गरी संस्करण (फिल्टरहरू libseccomp प्रयोग गरेर उत्पन्न गरिन्छ), डकरसँग seccomp को जडानको बारेमा कुरा गर्दछ, र धेरै उपयोगी लिङ्कहरू पनि प्रदान गर्दछ। लेखमा Systemd वा "तपाईलाई यसका लागि डकर आवश्यक पर्दैन!" सँग डेमनहरू अलग गर्दै। यसले कभर गर्दछ, विशेष गरी, प्रणालीमा चलिरहेको डेमनहरूको लागि प्रणाली कलहरूको कालोसूची वा ह्वाइटलिस्टहरू कसरी थप्ने।

अर्को हामी हेर्नेछौं कि कसरी फिल्टरहरू लेख्ने र लोड गर्ने seccomp खाली C मा र पुस्तकालय प्रयोग गर्दै libseccomp र प्रत्येक विकल्पको फाइदा र विपक्ष के हो, र अन्तमा, कार्यक्रम द्वारा सेकम्प कसरी प्रयोग गरिन्छ हेरौं। strace.

seccomp को लागि फिल्टरहरू लेखन र लोड गर्दै

BPF प्रोग्रामहरू कसरी लेख्ने भनेर हामीलाई पहिले नै थाहा छ, त्यसैले पहिले seccomp प्रोग्रामिङ इन्टरफेस हेरौं। तपाईंले प्रक्रिया स्तरमा फिल्टर सेट गर्न सक्नुहुन्छ, र सबै बाल प्रक्रियाहरूले प्रतिबन्धहरू इनहेरिट गर्नेछन्। यो प्रणाली कल प्रयोग गरेर गरिन्छ seccomp(2):

seccomp(SECCOMP_SET_MODE_FILTER, flags, &filter)

जहाँ &filter - यो हामीलाई पहिले नै परिचित संरचनाको लागि सूचक हो struct sock_fprog, अर्थात् BPF कार्यक्रम।

seccomp का लागि प्रोग्रामहरू सकेटका लागि कार्यक्रमहरूबाट कसरी भिन्न हुन्छन्? प्रसारित सन्दर्भ। सकेटको मामलामा, हामीलाई प्याकेट भएको मेमोरी क्षेत्र दिइएको थियो, र सेकम्पको मामलामा हामीलाई यस्तो संरचना दिइएको थियो।

struct seccomp_data {
    int   nr;
    __u32 arch;
    __u64 instruction_pointer;
    __u64 args[6];
};

यो छ nr प्रणाली कल को नम्बर सुरु गर्न को लागी छ, arch - हालको वास्तुकला (तल यस बारे थप), args - छवटा प्रणाली कल तर्कहरू, र instruction_pointer प्रणाली कल गर्ने प्रयोगकर्ता स्पेस निर्देशनको लागि सूचक हो। यसरी, उदाहरणका लागि, दर्तामा प्रणाली कल नम्बर लोड गर्न A हामीले भन्नु पर्छ

ldw [0]

त्यहाँ seccomp कार्यक्रमहरूका लागि अन्य सुविधाहरू छन्, उदाहरणका लागि, सन्दर्भ मात्र 32-बिट पङ्क्तिबद्धताद्वारा पहुँच गर्न सकिन्छ र तपाईंले आधा शब्द वा बाइट लोड गर्न सक्नुहुन्न - फिल्टर लोड गर्ने प्रयास गर्दा ldh [0] प्रणाली कल seccomp फर्कने छौ, फर्कने छन् EINVAL। प्रकार्यले लोड गरिएका फिल्टरहरू जाँच गर्दछ seccomp_check_filter() कर्नेलहरू। (रमाइलो कुरा हो, मूल कमिटमा जसले seccomp कार्यक्षमता थप्यो, तिनीहरूले यस प्रकार्यमा निर्देशन प्रयोग गर्न अनुमति थप्न बिर्से। mod (डिभिजन शेष) र अब सेकम्प बीपीएफ कार्यक्रमहरूको लागि अनुपलब्ध छ, यसको थपिएदेखि तोड्नेछ ABI।)

साधारणतया, हामी सेकम्प प्रोग्रामहरू लेख्न र पढ्नको लागि सबै कुरा जान्दछौं। सामान्यतया कार्यक्रम तर्कलाई प्रणाली कलहरूको सेतो वा कालो सूचीको रूपमा व्यवस्थित गरिएको छ, उदाहरणका लागि कार्यक्रम

ld [0]
jeq #304, bad
jeq #176, bad
jeq #239, bad
jeq #279, bad
good: ret #0x7fff0000 /* SECCOMP_RET_ALLOW */
bad: ret #0

304, 176, 239, 279 नम्बर भएका चार प्रणाली कलहरूको कालोसूची जाँच गर्दछ। यी प्रणाली कलहरू के हुन्? हामी निश्चित रूपमा भन्न सक्दैनौं, किनकि हामीलाई थाहा छैन कि कार्यक्रम कुन वास्तुकलाको लागि लेखिएको थियो। त्यसैले, seccomp को लेखकहरू प्रस्ताव सबै प्रोग्रामहरू आर्किटेक्चर जाँचको साथ सुरु गर्नुहोस् (हालको वास्तुकलालाई क्षेत्रको रूपमा सन्दर्भमा संकेत गरिएको छ। arch संरचना struct seccomp_data)। वास्तुकला जाँच संग, उदाहरण को शुरुवात यस्तो देखिन्छ:

ld [4]
jne #0xc000003e, bad_arch ; SCMP_ARCH_X86_64

र त्यसपछि हाम्रो प्रणाली कल नम्बरहरूले निश्चित मानहरू प्राप्त गर्नेछन्।

हामी seccomp प्रयोगको लागि फिल्टरहरू लेख्छौं र लोड गर्छौं libseccomp

नेटिभ कोडमा वा BPF असेंबलीमा फिल्टरहरू लेख्दा तपाईंलाई नतिजामा पूर्ण नियन्त्रण गर्न अनुमति दिन्छ, तर एकै समयमा, यो कहिलेकाहीं पोर्टेबल र/वा पढ्न योग्य कोड हुनु राम्रो हुन्छ। पुस्तकालयले हामीलाई यसमा मद्दत गर्नेछ libseccomp, जसले कालो वा सेतो फिल्टरहरू लेख्नको लागि मानक इन्टरफेस प्रदान गर्दछ।

आउनुहोस्, उदाहरणका लागि, प्रयोगकर्ताको छनौटको बाइनरी फाइल चलाउने प्रोग्राम लेखौं, पहिले नै प्रणाली कलहरूको कालोसूची स्थापना गरी माथिको लेख (कार्यक्रम अधिक पठनीयताको लागि सरलीकृत गरिएको छ, पूर्ण संस्करण फेला पार्न सकिन्छ यहाँ):

#include <seccomp.h>
#include <unistd.h>
#include <err.h>

static int sys_numbers[] = {
        __NR_mount,
        __NR_umount2,
       // ... еще 40 системных вызовов ...
        __NR_vmsplice,
        __NR_perf_event_open,
};

int main(int argc, char **argv)
{
        scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_ALLOW);

        for (size_t i = 0; i < sizeof(sys_numbers)/sizeof(sys_numbers[0]); i++)
                seccomp_rule_add(ctx, SCMP_ACT_TRAP, sys_numbers[i], 0);

        seccomp_load(ctx);

        execvp(argv[1], &argv[1]);
        err(1, "execlp: %s", argv[1]);
}

पहिले हामी एरे परिभाषित गर्छौं sys_numbers ब्लक गर्न 40+ प्रणाली कल नम्बरहरू। त्यसपछि, सन्दर्भ सुरु गर्नुहोस् ctx र पुस्तकालयलाई बताउनुहोस् हामी के अनुमति दिन चाहन्छौं (SCMP_ACT_ALLOW) पूर्वनिर्धारित रूपमा सबै प्रणाली कलहरू (कालोसूचीहरू निर्माण गर्न सजिलो छ)। त्यसपछि, एक एक गरेर, हामी कालोसूचीबाट सबै प्रणाली कलहरू थप्छौं। सूचीबाट प्रणाली कलको प्रतिक्रियामा, हामी अनुरोध गर्दछौं SCMP_ACT_TRAP, यस अवस्थामा seccomp ले प्रक्रियामा संकेत पठाउनेछ SIGSYS कुन प्रणाली कलले नियमहरू उल्लङ्घन गर्यो भन्ने विवरण सहित। अन्तमा, हामी प्रोग्राम प्रयोग गरेर कर्नेलमा लोड गर्छौं seccomp_load, जसले कार्यक्रम कम्पाइल गर्नेछ र प्रणाली कल प्रयोग गरेर प्रक्रियामा संलग्न गर्नेछ seccomp(2).

सफल संकलनको लागि, कार्यक्रमलाई पुस्तकालयसँग जोडिएको हुनुपर्छ libseccomp, उदाहरणका लागि:

cc -std=c17 -Wall -Wextra -c -o seccomp_lib.o seccomp_lib.c
cc -o seccomp_lib seccomp_lib.o -lseccomp

सफल प्रक्षेपणको उदाहरण:

$ ./seccomp_lib echo ok
ok

अवरुद्ध प्रणाली कलको उदाहरण:

$ sudo ./seccomp_lib mount -t bpf bpf /tmp
Bad system call

हामी प्रयोग गर्छौ straceविवरण को लागी:

$ sudo strace -e seccomp ./seccomp_lib mount -t bpf bpf /tmp
seccomp(SECCOMP_SET_MODE_FILTER, 0, {len=50, filter=0x55d8e78428e0}) = 0
--- SIGSYS {si_signo=SIGSYS, si_code=SYS_SECCOMP, si_call_addr=0xboobdeadbeef, si_syscall=__NR_mount, si_arch=AUDIT_ARCH_X86_64} ---
+++ killed by SIGSYS (core dumped) +++
Bad system call

हामी कसरी थाहा पाउन सक्छौं कि कार्यक्रम अवैध प्रणाली कल को प्रयोग को कारण समाप्त भएको थियो mount(2).

त्यसोभए, हामीले पुस्तकालय प्रयोग गरेर फिल्टर लेख्यौं libseccomp, गैर-तुच्छ कोडलाई चार रेखाहरूमा फिट गर्दै। माथिको उदाहरणमा, यदि त्यहाँ धेरै संख्यामा प्रणाली कलहरू छन् भने, कार्यान्वयन समय उल्लेखनीय रूपमा कम गर्न सकिन्छ, किनकि जाँच केवल तुलनाहरूको सूची हो। अनुकूलनको लागि, libseccomp हालै थियो प्याच समावेश, जसले फिल्टर विशेषताको लागि समर्थन थप्छ SCMP_FLTATR_CTL_OPTIMIZE। यो विशेषतालाई 2 मा सेट गर्नाले फिल्टरलाई बाइनरी खोज कार्यक्रममा रूपान्तरण गर्नेछ।

यदि तपाइँ बाइनरी खोज फिल्टरहरूले कसरी काम गर्छ भनेर हेर्न चाहनुहुन्छ भने, हेर्नुहोस् सरल लिपि, जसले प्रणाली कल नम्बरहरू डायल गरेर BPF एसेम्बलरमा त्यस्ता कार्यक्रमहरू उत्पन्न गर्दछ, उदाहरणका लागि:

$ echo 1 3 6 8 13 | ./generate_bin_search_bpf.py
ld [0]
jeq #6, bad
jgt #6, check8
jeq #1, bad
jeq #3, bad
ret #0x7fff0000
check8:
jeq #8, bad
jeq #13, bad
ret #0x7fff0000
bad: ret #0

केहि पनि छिटो लेख्न असम्भव छ, किनकि BPF कार्यक्रमहरूले इन्डेन्टेसन जम्पहरू प्रदर्शन गर्न सक्दैन (हामी गर्न सक्दैनौं, उदाहरणका लागि, jmp A वा jmp [label+X]) र त्यसैले सबै संक्रमणहरू स्थिर छन्।

seccomp र strace

उपयोगिता सबैलाई थाहा छ strace लिनक्स मा प्रक्रियाहरु को व्यवहार को अध्ययन को लागी एक अपरिहार्य उपकरण हो। यद्यपि, धेरैले सुनेका छन् प्रदर्शन मुद्दाहरू यो उपयोगिता प्रयोग गर्दा। तथ्य यो हो strace प्रयोग गरी कार्यान्वयन गरिएको छ ptrace(2), र यस संयन्त्रमा हामीले प्रक्रिया रोक्नको लागि प्रणाली कलहरूको कुन सेटमा निर्दिष्ट गर्न सक्दैनौं, उदाहरणका लागि, आदेशहरू

$ time strace du /usr/share/ >/dev/null 2>&1

real    0m3.081s
user    0m0.531s
sys     0m2.073s

и

$ time strace -e open du /usr/share/ >/dev/null 2>&1

real    0m2.404s
user    0m0.193s
sys     0m1.800s

लगभग एकै समयमा प्रशोधन गरिन्छ, यद्यपि दोस्रो अवस्थामा हामी केवल एक प्रणाली कल ट्रेस गर्न चाहन्छौं।

नयाँ विकल्प --seccomp-bpf, मा थपियो strace संस्करण 5.3, तपाईंलाई धेरै पटक प्रक्रियालाई गति दिन अनुमति दिन्छ र एक प्रणाली कलको ट्रेस अन्तर्गत स्टार्टअप समय पहिले नै नियमित स्टार्टअपको समयसँग तुलना गर्न सकिन्छ:

$ time strace --seccomp-bpf -e open du /usr/share/ >/dev/null 2>&1

real    0m0.148s
user    0m0.017s
sys     0m0.131s

$ time du /usr/share/ >/dev/null 2>&1

real    0m0.140s
user    0m0.024s
sys     0m0.116s

(यहाँ, अवश्य पनि, हामीले यस आदेशको मुख्य प्रणाली कल ट्रेस गर्दैनौं भन्नेमा थोरै छल छ। यदि हामीले ट्रेस गर्दै थियौं, उदाहरणका लागि, newfsstat, त्यसपछि strace बिना नै कडा ब्रेक हुनेछ --seccomp-bpf.)

यो विकल्प कसरी काम गर्छ? उनी बिना strace प्रक्रियामा जडान गर्दछ र यसलाई प्रयोग गर्न सुरु गर्दछ PTRACE_SYSCALL। जब एक व्यवस्थित प्रक्रियाले (कुनै) प्रणाली कल जारी गर्दछ, नियन्त्रण हस्तान्तरण गरिन्छ strace, जसले प्रणाली कलको तर्कहरू हेर्छ र यसलाई चलाउँछ PTRACE_SYSCALL। केहि समय पछि, प्रक्रियाले प्रणाली कल पूरा गर्दछ र जब यो बाहिर निस्कन्छ, नियन्त्रण पुन: स्थानान्तरण हुन्छ strace, जसले फिर्ती मानहरू हेर्छ र प्रयोग गरेर प्रक्रिया सुरु गर्दछ PTRACE_SYSCALL, र यस्तै।

सानाहरूका लागि BPF, भाग शून्य: क्लासिक BPF

seccomp को साथ, तथापि, यो प्रक्रिया ठ्याक्कै हामीले चाहेको रूपमा अनुकूलित गर्न सकिन्छ। अर्थात्, यदि हामी केवल प्रणाली कल हेर्न चाहन्छौं X, त्यसपछि हामी यसको लागि BPF फिल्टर लेख्न सक्छौं X मान फर्काउँछ SECCOMP_RET_TRACE, र हामीलाई चासो नहुने कलहरूको लागि - SECCOMP_RET_ALLOW:

ld [0]
jneq #X, ignore
trace: ret #0x7ff00000
ignore: ret #0x7fff0000

यस अवस्थामा strace प्रारम्भिक रूपमा प्रक्रिया सुरु हुन्छ PTRACE_CONT, हाम्रो फिल्टर प्रत्येक प्रणाली कलको लागि प्रशोधन गरिन्छ, यदि प्रणाली कल छैन भने X, त्यसपछि प्रक्रिया चलिरहन्छ, तर यदि यो X, त्यसपछि seccomp ले नियन्त्रण स्थानान्तरण गर्नेछ straceजसले तर्कहरू हेरेर प्रक्रिया सुरु गर्नेछ PTRACE_SYSCALL (seccomp सँग प्रणाली कलबाट बाहिर निस्कँदा प्रोग्राम चलाउन सक्ने क्षमता छैन)। जब प्रणाली कल फर्काउँछ, strace प्रयोग गरेर प्रक्रिया पुन: सुरु गर्नेछ PTRACE_CONT र seccomp बाट नयाँ सन्देशहरूको लागि प्रतीक्षा गर्नेछ।

सानाहरूका लागि BPF, भाग शून्य: क्लासिक BPF

विकल्प प्रयोग गर्दा --seccomp-bpf त्यहाँ दुई प्रतिबन्धहरू छन्। पहिले, पहिले नै अवस्थित प्रक्रियामा सामेल हुन सम्भव छैन (विकल्प -p कार्यक्रम strace), किनकि यो seccomp द्वारा समर्थित छैन। दोस्रो, कुनै सम्भावना छैन छैन बाल प्रक्रियाहरू हेर्नुहोस्, किनकि seccomp फिल्टरहरू यसलाई असक्षम पार्ने क्षमता बिना सबै बाल प्रक्रियाहरूद्वारा विरासतमा प्राप्त हुन्छन्।

कसरी ठ्याक्कै मा एक सानो थप विवरण strace सँग काम गर्दछ seccomp बाट पाउन सकिन्छ भर्खरको रिपोर्ट। हाम्रो लागि, सबैभन्दा चाखलाग्दो तथ्य यो हो कि seccomp द्वारा प्रतिनिधित्व गरिएको क्लासिक BPF आज पनि प्रयोग गरिन्छ।

xt_bpf

अब नेटवर्कको संसारमा फर्कौं।

पृष्ठभूमि: धेरै समय पहिले, 2007 मा, कोर थियो थपे मोड्युल xt_u32 नेटफिल्टरको लागि। यो अझ पुरानो ट्राफिक वर्गीकरणकर्ता संग समानता द्वारा लेखिएको थियो cls_u32 र तपाइँलाई निम्न सरल अपरेसनहरू प्रयोग गरेर iptables को लागि स्वेच्छाचारी बाइनरी नियमहरू लेख्न अनुमति दिनुभयो: प्याकेजबाट 32 बिट लोड गर्नुहोस् र तिनीहरूमा अंकगणित कार्यहरूको सेट प्रदर्शन गर्नुहोस्। उदाहरणका लागि,

sudo iptables -A INPUT -m u32 --u32 "6&0xFF=1" -j LOG --log-prefix "seen-by-xt_u32"

प्याडिङ ६ बाट सुरु हुँदै आईपी हेडरको ३२ बिट्स लोड गर्छ र तिनीहरूलाई मास्क लागू गर्छ। 0xFF (कम बाइट लिनुहोस्)। यो क्षेत्र protocol IP हेडर र हामी यसलाई 1 (ICMP) सँग तुलना गर्छौं। तपाईं एक नियममा धेरै चेकहरू संयोजन गर्न सक्नुहुन्छ, र तपाईं अपरेटरलाई पनि कार्यान्वयन गर्न सक्नुहुन्छ @ - X बाइटहरू दायाँतिर सार्नुहोस्। उदाहरणका लागि, नियम

iptables -m u32 --u32 "6&0xFF=0x6 && 0>>22&0x3C@4=0x29"

TCP अनुक्रम संख्या बराबर छैन भने जाँच गर्दछ 0x29। म थप विवरणहरूमा जाने छैन, किनकि यो पहिले नै स्पष्ट छ कि हातले त्यस्ता नियमहरू लेख्नु धेरै सुविधाजनक छैन। लेखमा BPF - बिर्सिएको बाइटकोड, त्यहाँ प्रयोग र नियम उत्पादनको उदाहरणका साथ धेरै लिङ्कहरू छन् xt_u32। यस लेखको अन्त्यमा लिङ्कहरू पनि हेर्नुहोस्।

२०१३ देखि मोड्युलको सट्टा मोड्युल xt_u32 तपाईं BPF आधारित मोड्युल प्रयोग गर्न सक्नुहुन्छ xt_bpf। यो सम्म पढेका जो कोहीले यसको सञ्चालनको सिद्धान्तको बारेमा पहिले नै स्पष्ट हुनुपर्दछ: iptables नियमहरूको रूपमा BPF bytecode चलाउनुहोस्। तपाईं नयाँ नियम सिर्जना गर्न सक्नुहुन्छ, उदाहरणका लागि, यो जस्तै:

iptables -A INPUT -m bpf --bytecode <байткод> -j LOG

यहाँ <байткод> - यो एसेम्बलर आउटपुट ढाँचामा कोड हो bpf_asm पूर्वनिर्धारित रूपमा, उदाहरणका लागि,

$ cat /tmp/test.bpf
ldb [9]
jneq #17, ignore
ret #1
ignore: ret #0

$ bpf_asm /tmp/test.bpf
4,48 0 0 9,21 0 1 17,6 0 0 1,6 0 0 0,

# iptables -A INPUT -m bpf --bytecode "$(bpf_asm /tmp/test.bpf)" -j LOG

यस उदाहरणमा हामी सबै UDP प्याकेटहरू फिल्टर गर्दैछौं। मोड्युलमा BPF कार्यक्रमको सन्दर्भ xt_bpf, अवश्य पनि, iptables को मामला मा, IPv4 हेडर को शुरुवातमा, प्याकेट डेटामा पोइन्ट गर्दछ। BPF कार्यक्रमबाट फिर्ता मूल्य बुलियनकहाँ false मतलब प्याकेट मेल खाएन।

यो स्पष्ट छ कि मोड्युल xt_bpf माथिको उदाहरण भन्दा बढी जटिल फिल्टरहरूलाई समर्थन गर्दछ। Cloudfare बाट वास्तविक उदाहरणहरू हेरौं। हालसम्म तिनीहरूले मोड्युल प्रयोग गरे xt_bpf DDoS आक्रमणहरू विरुद्ध सुरक्षा गर्न। लेखमा BPF उपकरणहरू प्रस्तुत गर्दै तिनीहरूले कसरी (र किन) BPF फिल्टरहरू उत्पन्न गर्छन् र त्यस्ता फिल्टरहरू सिर्जना गर्नका लागि उपयोगिताहरूको सेटमा लिङ्कहरू प्रकाशित गर्छन् भनेर व्याख्या गर्छन्। उदाहरणका लागि, उपयोगिता प्रयोग गर्दै bpfgen तपाईंले नामको लागि DNS क्वेरीसँग मेल खाने BPF कार्यक्रम सिर्जना गर्न सक्नुहुन्छ habr.com:

$ ./bpfgen --assembly dns -- habr.com
ldx 4*([0]&0xf)
ld #20
add x
tax

lb_0:
    ld [x + 0]
    jneq #0x04686162, lb_1
    ld [x + 4]
    jneq #0x7203636f, lb_1
    ldh [x + 8]
    jneq #0x6d00, lb_1
    ret #65535

lb_1:
    ret #0

कार्यक्रममा हामी पहिले दर्तामा लोड गर्छौं X लाइन ठेगानाको सुरुवात x04habrx03comx00 UDP डाटाग्राम भित्र र त्यसपछि अनुरोध जाँच गर्नुहोस्: 0x04686162 <-> "x04hab" र यति।

केहि समय पछि, क्लाउडफेयरले p0f -> BPF कम्पाइलर कोड प्रकाशित गर्यो। लेखमा p0f BPF कम्पाइलर प्रस्तुत गर्दै तिनीहरू p0f के हो र कसरी p0f हस्ताक्षर BPF मा रूपान्तरण गर्ने बारे कुरा गर्छन्:

$ ./bpfgen p0f -- 4:64:0:0:*,0::ack+:0
39,0 0 0 0,48 0 0 8,37 35 0 64,37 0 34 29,48 0 0 0,
84 0 0 15,21 0 31 5,48 0 0 9,21 0 29 6,40 0 0 6,
...

हाल Cloudfare प्रयोग गर्दैन xt_bpf, तिनीहरू XDP मा सारियो - BPF को नयाँ संस्करण प्रयोग गर्ने विकल्पहरू मध्ये एक, हेर्नुहोस्। L4Drop: XDP DDoS Mitigations.

cls_bpf

कर्नेलमा क्लासिक BPF प्रयोग गर्ने अन्तिम उदाहरण वर्गीकरणकर्ता हो cls_bpf लिनक्समा ट्राफिक कन्ट्रोल सबसिस्टमको लागि, २०१३ को अन्त्यमा लिनक्समा थपियो र अवधारणात्मक रूपमा पुरानो प्रतिस्थापन cls_u32.

यद्यपि, हामी अब कामको वर्णन गर्दैनौं cls_bpf, किनकि क्लासिक BPF को बारेमा ज्ञानको दृष्टिकोणबाट यसले हामीलाई केहि दिने छैन - हामी पहिले नै सबै प्रकार्यहरूसँग परिचित भइसकेका छौं। थप रूपमा, विस्तारित BPF को बारेमा कुरा गर्दै पछिका लेखहरूमा, हामी यो वर्गीकरणकर्तालाई एक पटक भन्दा बढी भेट्नेछौं।

क्लासिक BPF c प्रयोग गर्ने बारे कुरा नगर्ने अर्को कारण cls_bpf समस्या यो हो कि, विस्तारित BPF को तुलनामा, यस मामला मा लागू हुने दायरा मौलिक रूपमा संकुचित छ: शास्त्रीय कार्यक्रमहरूले प्याकेजहरूको सामग्री परिवर्तन गर्न सक्दैन र कलहरू बीचको अवस्था बचत गर्न सक्दैन।

त्यसैले यो क्लासिक BPF लाई अलविदा भन्न र भविष्य हेर्ने समय हो।

क्लासिक BPF को विदाई

नब्बे दशकको प्रारम्भमा विकसित भएको BPF प्रविधिले एक चौथाई शताब्दीसम्म सफलतापूर्वक बाँच्यो र अन्त्यसम्म नयाँ अनुप्रयोगहरू फेला पार्यो भनेर हामीले हेर्यौं। यद्यपि, स्ट्याक मेसिनहरूबाट RISC मा संक्रमण जस्तै, जसले क्लासिक BPF को विकासको लागि प्रोत्साहनको रूपमा काम गर्‍यो, 32 को दशकमा त्यहाँ 64-bit बाट XNUMX-bit मेसिनहरूमा संक्रमण भयो र क्लासिक BPF अप्रचलित हुन थाल्यो। थप रूपमा, क्लासिक BPF को क्षमताहरू धेरै सीमित छन्, र पुरानो वास्तुकलाको अतिरिक्त - हामीसँग BPF कार्यक्रमहरूमा कलहरू बीच राज्य बचत गर्ने क्षमता छैन, त्यहाँ प्रत्यक्ष प्रयोगकर्ता अन्तरक्रियाको कुनै सम्भावना छैन, अन्तरक्रिया गर्ने कुनै सम्भावना छैन। कर्नेलसँग, संरचना क्षेत्रहरूको सीमित संख्या पढ्न बाहेक sk_buff र सरल सहयोगी प्रकार्यहरू सुरु गर्दै, तपाईंले प्याकेटहरूको सामग्री परिवर्तन गर्न र तिनीहरूलाई पुन: निर्देशित गर्न सक्नुहुन्न।

वास्तवमा, हाल लिनक्समा क्लासिक BPF को बाँकी रहेका सबै API इन्टरफेस हो, र कर्नेल भित्र सबै क्लासिक प्रोग्रामहरू, सकेट फिल्टरहरू वा सेकम्प फिल्टरहरू, स्वचालित रूपमा नयाँ ढाँचा, विस्तारित BPF मा अनुवाद हुन्छन्। (हामी अर्को लेखमा यो कसरी हुन्छ भन्ने बारे कुरा गर्नेछौं।)

नयाँ वास्तुकलामा संक्रमण 2013 मा सुरु भयो, जब एलेक्सी स्टारोवोइटोभले बीपीएफ अपडेट योजना प्रस्ताव गरे। 2014 मा सम्बन्धित प्याचहरू देखिन थालेको छ कोर मा। जहाँसम्म मैले बुझेको छु, प्रारम्भिक योजना केवल आर्किटेक्चर र JIT कम्पाइलरलाई 64-बिट मेसिनहरूमा अधिक कुशलतापूर्वक चलाउनको लागि अनुकूलन गर्न थियो, तर यसको सट्टा यी अप्टिमाइजेसनहरूले लिनक्स विकासमा नयाँ अध्यायको सुरुवात चिन्ह लगाइयो।

यस शृङ्खलाका थप लेखहरूले नयाँ प्रविधिको वास्तुकला र अनुप्रयोगहरू समावेश गर्दछ, सुरुमा आन्तरिक BPF, त्यसपछि विस्तारित BPF, र अब केवल BPF भनिन्छ।

सन्दर्भ

  1. स्टीवन म्याकन र भ्यान ज्याकबसन, "द बीएसडी प्याकेट फिल्टर: प्रयोगकर्ता-स्तर प्याकेट क्याप्चरको लागि नयाँ वास्तुकला", https://www.tcpdump.org/papers/bpf-usenix93.pdf
  2. Steven McCanne, "libpcap: प्याकेट क्याप्चरको लागि एक वास्तुकला र अनुकूलन विधि", https://sharkfestus.wireshark.org/sharkfest.11/presentations/McCanne-Sharkfest'11_Keynote_Address.pdf
  3. tcpdump, libpcap: https://www.tcpdump.org/
  4. IPtable U32 मिलान ट्यूटोरियल.
  5. BPF - बिर्सिएको बाइटकोड: https://blog.cloudflare.com/bpf-the-forgotten-bytecode/
  6. BPF उपकरण परिचय: https://blog.cloudflare.com/introducing-the-bpf-tools/
  7. bpf_cls: http://man7.org/linux/man-pages/man8/tc-bpf.8.html
  8. एक seccomp सिंहावलोकन: https://lwn.net/Articles/656307/
  9. https://github.com/torvalds/linux/blob/master/Documentation/userspace-api/seccomp_filter.rst
  10. habr: कन्टेनर र सुरक्षा: seccomp
  11. habr: systemd वा "तपाईलाई यसका लागि डकरको आवश्यकता पर्दैन!" सँग डेमनहरू अलग गर्दै।
  12. पॉल चेगनन, "स्ट्रेस --सेकम्प-बीपीएफ: हुड मुनिको एक नजर", https://fosdem.org/2020/schedule/event/debugging_strace_bpf/
  13. netsniff-ng: http://netsniff-ng.org/

स्रोत: www.habr.com

एक टिप्पणी थप्न