சிறியவர்களுக்கான BPF, பகுதி ஒன்று: நீட்டிக்கப்பட்ட BPF

தொடக்கத்தில் ஒரு தொழில்நுட்பம் இருந்தது, அது பிபிஎஃப் என்று அழைக்கப்பட்டது. நாங்கள் அவளைப் பார்த்தோம் முந்தைய, இந்தத் தொடரின் பழைய ஏற்பாட்டு கட்டுரை. 2013 ஆம் ஆண்டில், அலெக்ஸி ஸ்டாரோவொய்டோவ் மற்றும் டேனியல் போர்க்மேன் ஆகியோரின் முயற்சியால், அதன் மேம்படுத்தப்பட்ட பதிப்பு, நவீன 64-பிட் இயந்திரங்களுக்கு உகந்ததாக, உருவாக்கப்பட்டு லினக்ஸ் கர்னலில் சேர்க்கப்பட்டது. இந்த புதிய தொழில்நுட்பம் சுருக்கமாக இன்டர்னல் பிபிஎஃப் என்று அழைக்கப்படுகிறது, பின்னர் விரிவாக்கப்பட்ட பிபிஎஃப் என்று மறுபெயரிடப்பட்டது, இப்போது, ​​பல ஆண்டுகளுக்குப் பிறகு, எல்லோரும் அதை பிபிஎஃப் என்று அழைக்கிறார்கள்.

தோராயமாகச் சொன்னால், லினக்ஸ் கர்னல் இடத்தில் தன்னிச்சையான பயனர் வழங்கிய குறியீட்டை இயக்க BPF உங்களை அனுமதிக்கிறது, மேலும் புதிய கட்டமைப்பு மிகவும் வெற்றிகரமாக மாறியது, அதன் அனைத்து பயன்பாடுகளையும் விவரிக்க எங்களுக்கு இன்னும் ஒரு டஜன் கட்டுரைகள் தேவைப்படும். (டெவலப்பர்கள் சிறப்பாகச் செய்யாத ஒரே விஷயம், கீழே உள்ள செயல்திறன் குறியீட்டில் நீங்கள் பார்க்கக்கூடியது, ஒரு நல்ல லோகோவை உருவாக்குவதுதான்.)

இந்தக் கட்டுரை BPF மெய்நிகர் இயந்திரத்தின் கட்டமைப்பு, BPF உடன் பணிபுரியும் கர்னல் இடைமுகங்கள், மேம்பாட்டுக் கருவிகள், அத்துடன் தற்போதுள்ள திறன்களின் சுருக்கமான, மிகச் சுருக்கமான கண்ணோட்டத்தை விவரிக்கிறது, அதாவது. BPF இன் நடைமுறை பயன்பாடுகள் பற்றிய ஆழமான ஆய்வுக்கு எதிர்காலத்தில் நமக்குத் தேவைப்படும் அனைத்தும்.
சிறியவர்களுக்கான BPF, பகுதி ஒன்று: நீட்டிக்கப்பட்ட BPF

கட்டுரையின் சுருக்கம்

BPF கட்டிடக்கலை அறிமுகம். முதலில், BPF கட்டிடக்கலையின் ஒரு பறவைக் கண்ணோட்டத்தை எடுத்து, முக்கிய கூறுகளை கோடிட்டுக் காட்டுவோம்.

BPF மெய்நிகர் இயந்திரத்தின் பதிவுகள் மற்றும் கட்டளை அமைப்பு. ஒட்டுமொத்த கட்டிடக்கலை பற்றிய யோசனை ஏற்கனவே உள்ளது, பிபிஎஃப் மெய்நிகர் இயந்திரத்தின் கட்டமைப்பை விவரிப்போம்.

BPF பொருள்களின் வாழ்க்கைச் சுழற்சி, bpffs கோப்பு முறைமை. இந்த பிரிவில், BPF பொருள்களின் வாழ்க்கைச் சுழற்சியை நாம் கூர்ந்து கவனிப்போம் - திட்டங்கள் மற்றும் வரைபடங்கள்.

பிபிஎஃப் சிஸ்டம் அழைப்பைப் பயன்படுத்தி பொருட்களை நிர்வகித்தல். ஏற்கனவே உள்ள கணினியைப் பற்றிய சில புரிதலுடன், ஒரு சிறப்பு கணினி அழைப்பைப் பயன்படுத்தி பயனர் இடத்திலிருந்து பொருட்களை எவ்வாறு உருவாக்குவது மற்றும் கையாளுவது என்பதை இறுதியாகப் பார்ப்போம். bpf(2).

Пишем программы BPF с помощью libbpf. நிச்சயமாக, நீங்கள் கணினி அழைப்பைப் பயன்படுத்தி நிரல்களை எழுதலாம். ஆனால் அது கடினம். மிகவும் யதார்த்தமான சூழ்நிலையில், அணுசக்தி நிரலாளர்கள் ஒரு நூலகத்தை உருவாக்கினர் libbpf. நாங்கள் ஒரு அடிப்படை BPF பயன்பாட்டு எலும்புக்கூட்டை உருவாக்குவோம், அதை அடுத்தடுத்த எடுத்துக்காட்டுகளில் பயன்படுத்துவோம்.

கர்னல் உதவியாளர்கள். கர்னல் ஹெல்பர் செயல்பாடுகளை BPF புரோகிராம்கள் எவ்வாறு அணுகலாம் என்பதை இங்கே கற்றுக்கொள்வோம் - இது வரைபடங்களுடன், கிளாசிக் ஒன்றோடு ஒப்பிடும்போது புதிய BPF இன் திறன்களை அடிப்படையில் விரிவாக்கும் ஒரு கருவியாகும்.

BPF திட்டங்களிலிருந்து வரைபடங்களுக்கான அணுகல். இந்த கட்டத்தில், வரைபடங்களைப் பயன்படுத்தும் நிரல்களை எவ்வாறு உருவாக்குவது என்பதைப் புரிந்துகொள்வதற்கு போதுமான அளவு அறிவோம். சிறந்த மற்றும் சக்திவாய்ந்த சரிபார்ப்பாளரைப் பற்றி விரைவாகப் பார்க்கலாம்.

மேம்பாட்டு கருவிகள். சோதனைகளுக்கு தேவையான பயன்பாடுகள் மற்றும் கர்னலை எவ்வாறு இணைப்பது என்பது பற்றிய உதவிப் பகுதி.

முடிவு. கட்டுரையின் முடிவில், இவ்வளவு தூரம் படித்தவர்கள் ஊக்கமளிக்கும் வார்த்தைகளையும் பின்வரும் கட்டுரைகளில் என்ன நடக்கும் என்பதற்கான சுருக்கமான விளக்கத்தையும் காணலாம். தொடர்ச்சிக்காக காத்திருக்கும் விருப்பமும் திறனும் இல்லாதவர்களுக்காக சுய ஆய்வுக்கான பல இணைப்புகளையும் பட்டியலிடுவோம்.

BPF கட்டிடக்கலை அறிமுகம்

BPF கட்டமைப்பைக் கருத்தில் கொள்ளத் தொடங்கும் முன், கடைசியாக (ஓ) ஒன்றைக் குறிப்பிடுவோம் கிளாசிக் BPF, இது RISC இயந்திரங்களின் வருகையின் பிரதிபலிப்பாக உருவாக்கப்பட்டது மற்றும் திறமையான பாக்கெட் வடிகட்டலின் சிக்கலைத் தீர்த்தது. கட்டிடக்கலை மிகவும் வெற்றிகரமாக மாறியது, தொண்ணூறுகளில் பெர்க்லி யுனிக்ஸ் இல் பிறந்ததால், இது ஏற்கனவே உள்ள பெரும்பாலான இயக்க முறைமைகளுக்கு மாற்றப்பட்டது, பைத்தியம் இருபதுகளில் உயிர் பிழைத்தது மற்றும் இன்னும் புதிய பயன்பாடுகளைக் கண்டுபிடித்து வருகிறது.

64-பிட் இயந்திரங்கள், கிளவுட் சேவைகள் மற்றும் SDN ஐ உருவாக்குவதற்கான கருவிகளின் தேவை அதிகரித்ததன் விளைவாக புதிய BPF உருவாக்கப்பட்டது.Sஅடிக்கடி-dசுத்திகரிக்கப்பட்டது nபணிபுரிதல்). கிளாசிக் BPFக்கு மேம்படுத்தப்பட்ட மாற்றாக கர்னல் நெட்வொர்க் பொறியாளர்களால் உருவாக்கப்பட்டது, புதிய BPF ஆனது ஆறு மாதங்களுக்குப் பிறகு லினக்ஸ் சிஸ்டம்களைக் கண்டறியும் கடினமான பணியில் பயன்பாடுகளைக் கண்டறிந்தது, இப்போது, ​​அதன் தோற்றத்திற்கு ஆறு ஆண்டுகளுக்குப் பிறகு, எங்களுக்கு அடுத்த கட்டுரை முழுவதும் தேவைப்படும். பல்வேறு வகையான நிரல்களை பட்டியலிடுங்கள்.

வேடிக்கையான படங்கள்

அதன் மையத்தில், BPF என்பது சாண்ட்பாக்ஸ் மெய்நிகர் இயந்திரமாகும், இது பாதுகாப்பை சமரசம் செய்யாமல் கர்னல் இடத்தில் "தன்னிச்சையான" குறியீட்டை இயக்க அனுமதிக்கிறது. BPF நிரல்கள் பயனர் இடத்தில் உருவாக்கப்பட்டு, கர்னலில் ஏற்றப்பட்டு, சில நிகழ்வு மூலத்துடன் இணைக்கப்படுகின்றன. ஒரு நிகழ்வு, எடுத்துக்காட்டாக, ஒரு பிணைய இடைமுகத்திற்கு ஒரு பாக்கெட்டை வழங்குதல், சில கர்னல் செயல்பாட்டைத் தொடங்குதல் போன்றவை. ஒரு தொகுப்பைப் பொறுத்தவரை, BPF நிரல் தொகுப்பின் தரவு மற்றும் மெட்டாடேட்டாவை அணுகும் (படிப்பதற்கும், எழுதுவதற்கும், நிரலின் வகையைப் பொறுத்து); கர்னல் செயல்பாட்டை இயக்கும் விஷயத்தில், வாதங்கள் செயல்பாடு, கர்னல் நினைவகத்திற்கான சுட்டிகள் உட்பட.

இந்த செயல்முறையை இன்னும் விரிவாகப் பார்ப்போம். தொடங்குவதற்கு, கிளாசிக் BPF இலிருந்து முதல் வேறுபாட்டைப் பற்றி பேசலாம், அதற்கான திட்டங்கள் அசெம்பிளரில் எழுதப்பட்டன. புதிய பதிப்பில், கட்டிடக்கலை விரிவுபடுத்தப்பட்டது, அதனால் நிரல்களை உயர்நிலை மொழிகளில் எழுத முடியும், முதன்மையாக, நிச்சயமாக, C இல். இதற்காக, llvm க்கான பின்தளம் உருவாக்கப்பட்டது, இது BPF கட்டமைப்பிற்கான பைட்கோடை உருவாக்க உங்களை அனுமதிக்கிறது.

சிறியவர்களுக்கான BPF, பகுதி ஒன்று: நீட்டிக்கப்பட்ட BPF

BPF கட்டமைப்பு நவீன இயந்திரங்களில் திறமையாக இயங்கும் வகையில் வடிவமைக்கப்பட்டுள்ளது. இதை நடைமுறையில் செயல்படுத்த, BPF பைட்கோடு, கர்னலில் ஏற்றப்பட்டவுடன், JIT கம்பைலர் (JIT compiler) எனப்படும் கூறுகளைப் பயன்படுத்தி நேட்டிவ் குறியீட்டில் மொழிபெயர்க்கப்படுகிறது.Jவிக்கட்டுகளால் In Time). அடுத்து, நீங்கள் நினைவில் வைத்திருந்தால், கிளாசிக் BPF இல் நிரல் கர்னலில் ஏற்றப்பட்டது மற்றும் நிகழ்வு மூலத்துடன் அணு ரீதியாக இணைக்கப்பட்டது - ஒற்றை கணினி அழைப்பின் சூழலில். புதிய கட்டமைப்பில், இது இரண்டு நிலைகளில் நடக்கும் - முதலில், கணினி அழைப்பைப் பயன்படுத்தி குறியீடு கர்னலில் ஏற்றப்படுகிறது. bpf(2)பின்னர், பின்னர், நிரலின் வகையைப் பொறுத்து மாறுபடும் பிற வழிமுறைகள் மூலம், நிரல் நிகழ்வு மூலத்துடன் இணைகிறது.

இங்கே வாசகருக்கு ஒரு கேள்வி இருக்கலாம்: இது சாத்தியமா? அத்தகைய குறியீட்டின் செயல்பாட்டின் பாதுகாப்பு எவ்வாறு உத்தரவாதம் அளிக்கப்படுகிறது? சரிபார்ப்பு (ஆங்கிலத்தில் இந்த நிலை சரிபார்ப்பு என்று அழைக்கப்படுகிறது, மேலும் நான் தொடர்ந்து ஆங்கில வார்த்தையைப் பயன்படுத்துவேன்):

சிறியவர்களுக்கான BPF, பகுதி ஒன்று: நீட்டிக்கப்பட்ட BPF

சரிபார்ப்பு என்பது ஒரு நிலையான பகுப்பாய்வி ஆகும், இது ஒரு நிரல் கர்னலின் இயல்பான செயல்பாட்டை சீர்குலைக்காது என்பதை உறுதி செய்கிறது. இதன் மூலம், நிரல் கணினியின் செயல்பாட்டில் தலையிட முடியாது என்று அர்த்தமல்ல - பிபிஎஃப் நிரல்கள், வகையைப் பொறுத்து, கர்னல் நினைவகத்தின் பிரிவுகளைப் படிக்கலாம் மற்றும் மீண்டும் எழுதலாம், செயல்பாடுகளின் மதிப்புகளைத் திரும்பப் பெறலாம், ஒழுங்கமைக்கலாம், சேர்க்கலாம், மீண்டும் எழுதலாம். மற்றும் முன்னோக்கி நெட்வொர்க் பாக்கெட்டுகள் கூட. BPF நிரலை இயக்குவது கர்னலை செயலிழக்கச் செய்யாது என்றும், விதிகளின்படி, எழுதும் அணுகலைக் கொண்ட ஒரு நிரல், எடுத்துக்காட்டாக, வெளிச்செல்லும் பாக்கெட்டின் தரவு, பாக்கெட்டுக்கு வெளியே கர்னல் நினைவகத்தை மேலெழுத முடியாது என்றும் சரிபார்ப்பு உத்தரவாதம் அளிக்கிறது. BPF இன் மற்ற அனைத்து கூறுகளையும் நாம் அறிந்த பிறகு, சரிபார்ப்பை தொடர்புடைய பிரிவில் இன்னும் கொஞ்சம் விரிவாகப் பார்ப்போம்.

அப்படியானால் இதுவரை நாம் என்ன கற்றுக்கொண்டோம்? பயனர் C இல் ஒரு நிரலை எழுதுகிறார், கணினி அழைப்பைப் பயன்படுத்தி அதை கர்னலில் ஏற்றுகிறார் bpf(2), இது ஒரு சரிபார்ப்பாளரால் சரிபார்க்கப்பட்டு சொந்த பைட்கோடில் மொழிபெயர்க்கப்படுகிறது. பின்னர் அதே அல்லது மற்றொரு பயனர் நிரலை நிகழ்வு மூலத்துடன் இணைத்து அதை இயக்கத் தொடங்குகிறது. துவக்க மற்றும் இணைப்பைப் பிரிப்பது பல காரணங்களுக்காக அவசியம். முதலாவதாக, சரிபார்ப்பை இயக்குவது ஒப்பீட்டளவில் விலை உயர்ந்தது மற்றும் அதே நிரலை பல முறை பதிவிறக்கம் செய்வதன் மூலம் கணினி நேரத்தை வீணடிக்கிறோம். இரண்டாவதாக, ஒரு நிரல் எவ்வாறு இணைக்கப்பட்டுள்ளது என்பது அதன் வகையைப் பொறுத்தது, மேலும் ஒரு வருடத்திற்கு முன்பு உருவாக்கப்பட்ட ஒரு "உலகளாவிய" இடைமுகம் புதிய வகை நிரல்களுக்கு ஏற்றதாக இருக்காது. (இப்போது கட்டிடக்கலை மிகவும் முதிர்ச்சியடைந்தாலும், இந்த இடைமுகத்தை மட்டத்தில் ஒருங்கிணைக்க ஒரு யோசனை உள்ளது libbpf.)

நாம் இன்னும் படங்களை முடிக்கவில்லை என்பதை கவனமுள்ள வாசகர் கவனிக்கலாம். உண்மையில், கிளாசிக் BPF உடன் ஒப்பிடும்போது BPF அடிப்படையில் படத்தை ஏன் மாற்றுகிறது என்பதை மேலே உள்ள அனைத்தும் விளக்கவில்லை. பொருந்தக்கூடிய தன்மையை கணிசமாக விரிவுபடுத்தும் இரண்டு கண்டுபிடிப்புகள் பகிரப்பட்ட நினைவகம் மற்றும் கர்னல் உதவி செயல்பாடுகளைப் பயன்படுத்தும் திறன் ஆகும். BPF இல், பகிரப்பட்ட நினைவகம் வரைபடங்கள் என்று அழைக்கப்படுவதைப் பயன்படுத்தி செயல்படுத்தப்படுகிறது - ஒரு குறிப்பிட்ட API உடன் பகிரப்பட்ட தரவு கட்டமைப்புகள். தோன்றிய முதல் வகை வரைபடம் ஹாஷ் அட்டவணையாக இருந்ததால் அவர்கள் இந்தப் பெயரைப் பெற்றிருக்கலாம். பின்னர் வரிசைகள் தோன்றின, உள்ளூர் (ஒரு-CPU) ஹாஷ் அட்டவணைகள் மற்றும் உள்ளூர் அணிவரிசைகள், தேடல் மரங்கள், BPF நிரல்களுக்கான சுட்டிகள் கொண்ட வரைபடங்கள் மற்றும் பல. இப்போது எங்களுக்கு சுவாரஸ்யமான விஷயம் என்னவென்றால், BPF திட்டங்கள் இப்போது அழைப்புகளுக்கு இடையில் நிலைத்திருக்கும் திறனைக் கொண்டுள்ளன மற்றும் பிற நிரல்களுடன் மற்றும் பயனர் இடத்துடன் பகிர்ந்து கொள்ள முடியும்.

கணினி அழைப்பைப் பயன்படுத்தி பயனர் செயல்முறைகளில் இருந்து வரைபடம் அணுகப்படுகிறது bpf(2), மற்றும் ஹெல்பர் செயல்பாடுகளைப் பயன்படுத்தி கர்னலில் இயங்கும் BPF நிரல்களிலிருந்து. மேலும், வரைபடங்களுடன் பணிபுரிய மட்டுமின்றி, பிற கர்னல் திறன்களை அணுகுவதற்கும் உதவியாளர்கள் உள்ளனர். எடுத்துக்காட்டாக, பிற இடைமுகங்களுக்கு பாக்கெட்டுகளை அனுப்புவதற்கும், perf நிகழ்வுகளை உருவாக்குவதற்கும், கர்னல் கட்டமைப்புகளை அணுகுவதற்கும் மற்றும் பலவற்றிற்கும் BPF நிரல்கள் உதவி செயல்பாடுகளைப் பயன்படுத்தலாம்.

சிறியவர்களுக்கான BPF, பகுதி ஒன்று: நீட்டிக்கப்பட்ட BPF

சுருக்கமாக, BPF தன்னிச்சையான, அதாவது, சரிபார்ப்பாளர்-சோதனை செய்யப்பட்ட, பயனர் குறியீட்டை கர்னல் இடத்தில் ஏற்றும் திறனை வழங்குகிறது. இந்தக் குறியீடு அழைப்புகளுக்கு இடையே நிலையைச் சேமிக்கும் மற்றும் பயனர் இடத்துடன் தரவைப் பரிமாறிக்கொள்ளும், மேலும் இந்த வகை நிரல்களால் அனுமதிக்கப்படும் கர்னல் துணை அமைப்புகளுக்கான அணுகலையும் கொண்டுள்ளது.

இது ஏற்கனவே கர்னல் தொகுதிகள் வழங்கிய திறன்களைப் போலவே உள்ளது, அதனுடன் ஒப்பிடும்போது BPF சில நன்மைகளைக் கொண்டுள்ளது (நிச்சயமாக, நீங்கள் ஒத்த பயன்பாடுகளை மட்டுமே ஒப்பிட முடியும், எடுத்துக்காட்டாக, கணினி தடமறிதல் - நீங்கள் BPF உடன் தன்னிச்சையான இயக்கியை எழுத முடியாது). குறைந்த நுழைவு வரம்பை நீங்கள் கவனிக்கலாம் (பிபிஎஃப் பயன்படுத்தும் சில பயன்பாடுகளுக்கு பயனர் கர்னல் நிரலாக்க திறன்கள் அல்லது பொதுவாக நிரலாக்க திறன்கள் தேவையில்லை), இயக்க நேர பாதுகாப்பு (எழுதும் போது கணினியை உடைக்காதவர்களுக்கான கருத்துகளில் உங்கள் கையை உயர்த்தவும். அல்லது சோதனை தொகுதிகள்), அணுத்தன்மை - தொகுதிகளை மீண்டும் ஏற்றும் போது வேலையில்லா நேரம் உள்ளது, மேலும் BPF துணை அமைப்பு எந்த நிகழ்வுகளும் தவறவிடப்படுவதை உறுதி செய்கிறது (நியாயமாகச் சொல்வதானால், இது அனைத்து வகையான BPF திட்டங்களுக்கும் பொருந்தாது).

இத்தகைய திறன்களின் இருப்பு கர்னலை விரிவுபடுத்துவதற்கான உலகளாவிய கருவியாக BPF ஐ உருவாக்குகிறது, இது நடைமுறையில் உறுதிப்படுத்தப்பட்டுள்ளது: BPF இல் மேலும் மேலும் புதிய வகையான திட்டங்கள் சேர்க்கப்படுகின்றன, மேலும் மேலும் பெரிய நிறுவனங்கள் BPF ஐ போர் சேவையகங்களில் 24×7 பயன்படுத்துகின்றன. ஸ்டார்ட்அப்கள் பிபிஎஃப் அடிப்படையிலான தீர்வுகளின் அடிப்படையில் தங்கள் வணிகத்தை உருவாக்குகின்றன. BPF எல்லா இடங்களிலும் பயன்படுத்தப்படுகிறது: DDoS தாக்குதல்களுக்கு எதிராக பாதுகாப்பது, SDN ஐ உருவாக்குதல் (உதாரணமாக, kubernetes க்கான நெட்வொர்க்குகளை செயல்படுத்துதல்), முக்கிய கணினி தடமறிதல் கருவி மற்றும் புள்ளியியல் சேகரிப்பான், ஊடுருவல் கண்டறிதல் அமைப்புகள் மற்றும் சாண்ட்பாக்ஸ் அமைப்புகள் போன்றவை.

கட்டுரையின் மேலோட்டப் பகுதியை இங்கே முடித்துவிட்டு, மெய்நிகர் இயந்திரம் மற்றும் BPF சுற்றுச்சூழல் அமைப்பை இன்னும் விரிவாகப் பார்ப்போம்.

திசைதிருப்பல்: பயன்பாடுகள்

பின்வரும் பிரிவுகளில் எடுத்துக்காட்டுகளை இயக்க, உங்களுக்கு குறைந்தபட்சம் பல பயன்பாடுகள் தேவைப்படலாம் llvm/clang bpf ஆதரவுடன் மற்றும் bpftool... அத்தியாயத்தில் மேம்பாட்டு கருவிகள் பயன்பாடுகளை அசெம்பிள் செய்வதற்கான வழிமுறைகளையும், உங்கள் கர்னலையும் நீங்கள் படிக்கலாம். எங்கள் விளக்கக்காட்சியின் இணக்கத்திற்கு இடையூறு ஏற்படாத வகையில் இந்தப் பகுதி கீழே வைக்கப்பட்டுள்ளது.

BPF மெய்நிகர் இயந்திரப் பதிவுகள் மற்றும் அறிவுறுத்தல் அமைப்பு

BPF இன் கட்டமைப்பு மற்றும் கட்டளை அமைப்பு நிரல்கள் C மொழியில் எழுதப்படும் மற்றும் கர்னலில் ஏற்றப்பட்ட பிறகு, சொந்த குறியீட்டில் மொழிபெயர்க்கப்படும் என்ற உண்மையை கணக்கில் எடுத்துக்கொண்டு உருவாக்கப்பட்டது. எனவே, பதிவுகளின் எண்ணிக்கை மற்றும் கட்டளைகளின் தொகுப்பு ஆகியவை நவீன இயந்திரங்களின் திறன்களின் கணித அர்த்தத்தில், குறுக்குவெட்டுக்கு ஒரு கண் கொண்டு தேர்ந்தெடுக்கப்பட்டன. கூடுதலாக, நிரல்களுக்கு பல்வேறு கட்டுப்பாடுகள் விதிக்கப்பட்டன, எடுத்துக்காட்டாக, சமீப காலம் வரை சுழல்கள் மற்றும் துணை நிரல்களை எழுதுவது சாத்தியமில்லை, மேலும் அறிவுறுத்தல்களின் எண்ணிக்கை 4096 ஆக வரையறுக்கப்பட்டுள்ளது (இப்போது சலுகை பெற்ற நிரல்கள் ஒரு மில்லியன் வழிமுறைகளை ஏற்றலாம்).

BPF பதினொரு பயனர் அணுகக்கூடிய 64-பிட் பதிவேடுகளைக் கொண்டுள்ளது r0-r10 மற்றும் ஒரு நிரல் கவுண்டர். பதிவு r10 பிரேம் பாயிண்டரைக் கொண்டுள்ளது மற்றும் படிக்க மட்டுமே. நிரல்கள் இயக்க நேரத்தில் 512-பைட் ஸ்டாக் மற்றும் வரைபட வடிவில் வரம்பற்ற அளவு பகிரப்பட்ட நினைவகத்திற்கான அணுகலைக் கொண்டுள்ளன.

BPF நிரல்கள் ஒரு குறிப்பிட்ட நிரல்-வகை கர்னல் உதவியாளர்களை இயக்க அனுமதிக்கப்படுகின்றன, மேலும் சமீபத்தில், வழக்கமான செயல்பாடுகள். ஒவ்வொரு செயல்பாடும் பதிவுகளில் அனுப்பப்பட்ட ஐந்து வாதங்கள் வரை எடுக்கலாம் r1-r5, மற்றும் திரும்ப மதிப்பு அனுப்பப்பட்டது r0. செயல்பாட்டிலிருந்து திரும்பிய பிறகு, பதிவேடுகளின் உள்ளடக்கங்கள் என்று உத்தரவாதம் அளிக்கப்படுகிறது r6-r9 மாறாது.

திறமையான நிரல் மொழிபெயர்ப்புக்கு, பதிவுகள் r0-r11 தற்போதைய கட்டமைப்பின் ABI அம்சங்களை கணக்கில் எடுத்துக்கொண்டு, ஆதரிக்கப்படும் அனைத்து கட்டமைப்புகளும் உண்மையான பதிவேடுகளுக்கு தனித்துவமாக வரைபடமாக்கப்பட்டுள்ளன. உதாரணமாக, க்கான x86_64 பதிவு செய்கிறது r1-r5, செயல்பாடு அளவுருக்களை அனுப்ப பயன்படுகிறது, காட்டப்படும் rdi, rsi, rdx, rcx, r8, செயல்பாடுகளுக்கு அளவுருக்களை அனுப்பப் பயன்படுகிறது x86_64. எடுத்துக்காட்டாக, இடதுபுறத்தில் உள்ள குறியீடானது வலதுபுறத்தில் உள்ள குறியீட்டை இவ்வாறு மொழிபெயர்க்கிறது:

1:  (b7) r1 = 1                    mov    $0x1,%rdi
2:  (b7) r2 = 2                    mov    $0x2,%rsi
3:  (b7) r3 = 3                    mov    $0x3,%rdx
4:  (b7) r4 = 4                    mov    $0x4,%rcx
5:  (b7) r5 = 5                    mov    $0x5,%r8
6:  (85) call pc+1                 callq  0x0000000000001ee8

பதிவு r0 நிரல் செயல்படுத்தலின் முடிவையும், பதிவேட்டில் திரும்பப் பெறவும் பயன்படுகிறது r1 நிரல் சூழலுக்கு ஒரு சுட்டிக்காட்டி அனுப்பப்படுகிறது - நிரலின் வகையைப் பொறுத்து, இது ஒரு கட்டமைப்பாக இருக்கலாம் struct xdp_md (XDPக்கு) அல்லது கட்டமைப்பு struct __sk_buff (வெவ்வேறு நெட்வொர்க் நிரல்களுக்கு) அல்லது கட்டமைப்பு struct pt_regs (பல்வேறு வகையான டிரேசிங் புரோகிராம்களுக்கு) போன்றவை.

எனவே, எங்களிடம் பதிவேடுகள், கர்னல் உதவியாளர்கள், ஒரு அடுக்கு, ஒரு சூழல் சுட்டிக்காட்டி மற்றும் வரைபடங்களின் வடிவத்தில் பகிர்ந்த நினைவகம் ஆகியவை இருந்தன. பயணத்தில் இவை அனைத்தும் முற்றிலும் அவசியம் என்று இல்லை, ஆனால் ...

விளக்கத்தைத் தொடரலாம் மற்றும் இந்த பொருள்களுடன் பணிபுரியும் கட்டளை அமைப்பு பற்றி பேசலாம். அனைத்தும் (கிட்டத்தட்ட அனைத்து) BPF அறிவுறுத்தல்கள் நிலையான 64-பிட் அளவைக் கொண்டுள்ளன. 64-பிட் பிக் எண்டியன் மெஷினில் ஒரு அறிவுறுத்தலைப் பார்த்தால் நீங்கள் பார்ப்பீர்கள்

சிறியவர்களுக்கான BPF, பகுதி ஒன்று: நீட்டிக்கப்பட்ட BPF

இது Code - இது அறிவுறுத்தலின் குறியாக்கம், Dst/Src முறையே பெறுநர் மற்றும் மூலத்தின் குறியாக்கங்கள், Off - 16-பிட் கையொப்பமிடப்பட்ட உள்தள்ளல், மற்றும் Imm சில வழிமுறைகளில் பயன்படுத்தப்படும் 32-பிட் கையொப்பமிடப்பட்ட முழு எண் (cBPF மாறிலி K போன்றது). குறியாக்கம் Code இரண்டு வகைகளில் ஒன்று உள்ளது:

சிறியவர்களுக்கான BPF, பகுதி ஒன்று: நீட்டிக்கப்பட்ட BPF

அறிவுறுத்தல் வகுப்புகள் 0, 1, 2, 3 நினைவகத்துடன் வேலை செய்வதற்கான கட்டளைகளை வரையறுக்கின்றன. அவர்கள் அழைக்கப்படுகின்றன, BPF_LD, BPF_LDX, BPF_ST, BPF_STX, முறையே. வகுப்புகள் 4, 7 (BPF_ALU, BPF_ALU64) ALU வழிமுறைகளின் தொகுப்பாகும். வகுப்புகள் 5, 6 (BPF_JMP, BPF_JMP32) ஜம்ப் வழிமுறைகளைக் கொண்டுள்ளது.

BPF அறிவுறுத்தல் முறையைப் படிப்பதற்கான கூடுதல் திட்டம் பின்வருமாறு: அனைத்து வழிமுறைகளையும் அவற்றின் அளவுருக்களையும் உன்னிப்பாகப் பட்டியலிடுவதற்குப் பதிலாக, இந்த பிரிவில் உள்ள இரண்டு எடுத்துக்காட்டுகளைப் பார்ப்போம், அவற்றிலிருந்து வழிமுறைகள் உண்மையில் எவ்வாறு செயல்படுகின்றன, எப்படி செய்வது என்பது தெளிவாகிறது. BPF க்கான எந்த பைனரி கோப்பையும் கைமுறையாக பிரிக்கவும். கட்டுரையின் பின்னர் உள்ளடக்கத்தை ஒருங்கிணைக்க, சரிபார்ப்பு, JIT கம்பைலர், கிளாசிக் BPF இன் மொழிபெயர்ப்பு, அத்துடன் வரைபடங்களைப் படிக்கும் போது, ​​அழைப்பு செயல்பாடுகள் போன்றவற்றைப் பற்றிய பிரிவுகளில் தனிப்பட்ட வழிமுறைகளையும் சந்திப்போம்.

தனிப்பட்ட வழிமுறைகளைப் பற்றி பேசும்போது, ​​முக்கிய கோப்புகளைப் பார்ப்போம் bpf.h и bpf_common.h, இது BPF வழிமுறைகளின் எண் குறியீடுகளை வரையறுக்கிறது. உங்கள் சொந்த மற்றும்/அல்லது பைனரிகளைப் பாகுபடுத்தும் போது கட்டிடக்கலையைப் படிக்கும் போது, ​​பின்வரும் ஆதாரங்களில் நீங்கள் சொற்பொருளை சிக்கலான வரிசையில் வரிசைப்படுத்தலாம்: அதிகாரப்பூர்வமற்ற eBPF விவரக்குறிப்பு, BPF மற்றும் XDP குறிப்பு வழிகாட்டி, அறிவுறுத்தல் தொகுப்பு, ஆவணப்படுத்தல்/நெட்வொர்க்கிங்/filter.txt மற்றும், நிச்சயமாக, லினக்ஸ் மூலக் குறியீட்டில் - சரிபார்ப்பாளர், JIT, BPF மொழிபெயர்ப்பாளர்.

எடுத்துக்காட்டு: உங்கள் தலையில் பிபிஎஃப் பிரித்தெடுத்தல்

ஒரு நிரலை தொகுக்கும் உதாரணத்தைப் பார்ப்போம் readelf-example.c மற்றும் விளைவாக பைனரி பார்க்க. அசல் உள்ளடக்கத்தை வெளிப்படுத்துவோம் readelf-example.c கீழே, பைனரி குறியீடுகளிலிருந்து அதன் தர்க்கத்தை மீட்டெடுத்த பிறகு:

$ clang -target bpf -c readelf-example.c -o readelf-example.o -O2
$ llvm-readelf -x .text readelf-example.o
Hex dump of section '.text':
0x00000000 b7000000 01000000 15010100 00000000 ................
0x00000010 b7000000 02000000 95000000 00000000 ................

வெளியீட்டில் முதல் நெடுவரிசை readelf ஒரு உள்தள்ளல் மற்றும் எங்கள் நிரல் நான்கு கட்டளைகளைக் கொண்டுள்ளது:

Code Dst Src Off  Imm
b7   0   0   0000 01000000
15   0   1   0100 00000000
b7   0   0   0000 02000000
95   0   0   0000 00000000

கட்டளை குறியீடுகள் சமம் b7, 15, b7 и 95. குறைந்தது குறிப்பிடத்தக்க மூன்று பிட்கள் அறிவுறுத்தல் வகுப்பு என்பதை நினைவில் கொள்க. எங்கள் விஷயத்தில், அனைத்து வழிமுறைகளிலும் நான்காவது பிட் காலியாக உள்ளது, எனவே அறிவுறுத்தல் வகுப்புகள் முறையே 7, 5, 7, 5 ஆகும். வகுப்பு 7 BPF_ALU64, மற்றும் 5 ஆகும் BPF_JMP. இரண்டு வகுப்புகளுக்கும், அறிவுறுத்தல் வடிவம் ஒன்றுதான் (மேலே பார்க்கவும்) மேலும் எங்கள் நிரலை இப்படி மாற்றி எழுதலாம் (அதே நேரத்தில் மீதமுள்ள நெடுவரிசைகளை மனித வடிவத்தில் மீண்டும் எழுதுவோம்):

Op S  Class   Dst Src Off  Imm
b  0  ALU64   0   0   0    1
1  0  JMP     0   1   1    0
b  0  ALU64   0   0   0    2
9  0  JMP     0   0   0    0

அறுவை சிகிச்சை b வர்க்கம் ALU64 - அது BPF_MOV. இது இலக்கு பதிவேட்டிற்கு ஒரு மதிப்பை வழங்குகிறது. பிட் அமைக்கப்பட்டால் s (ஆதாரம்), பின்னர் மதிப்பு மூலப் பதிவேட்டில் இருந்து எடுக்கப்பட்டது, மேலும் எங்கள் விஷயத்தில் அது அமைக்கப்படவில்லை என்றால், புலத்திலிருந்து மதிப்பு எடுக்கப்படும் Imm. எனவே முதல் மற்றும் மூன்றாவது வழிமுறைகளில் நாங்கள் செயல்பாட்டைச் செய்கிறோம் r0 = Imm. மேலும், JMP வகுப்பு 1 செயல்பாடு BPF_JEQ (சமமாக இருந்தால் குதிக்கவும்). எங்கள் விஷயத்தில், பிட் இருந்து S பூஜ்ஜியமாகும், இது மூலப் பதிவேட்டின் மதிப்பை புலத்துடன் ஒப்பிடுகிறது Imm. மதிப்புகள் இணைந்தால், மாற்றம் ஏற்படுகிறது PC + Offஅங்கு PC, வழக்கம் போல், அடுத்த அறிவுறுத்தலின் முகவரியைக் கொண்டுள்ளது. இறுதியாக, JMP வகுப்பு 9 ஆபரேஷன் ஆகும் BPF_EXIT. இந்த அறிவுறுத்தல் நிரலை முடித்து, கர்னலுக்குத் திரும்புகிறது r0. எங்கள் அட்டவணையில் ஒரு புதிய நெடுவரிசையைச் சேர்ப்போம்:

Op    S  Class   Dst Src Off  Imm    Disassm
MOV   0  ALU64   0   0   0    1      r0 = 1
JEQ   0  JMP     0   1   1    0      if (r1 == 0) goto pc+1
MOV   0  ALU64   0   0   0    2      r0 = 2
EXIT  0  JMP     0   0   0    0      exit

இதை மிகவும் வசதியான வடிவத்தில் மீண்டும் எழுதலாம்:

     r0 = 1
     if (r1 == 0) goto END
     r0 = 2
END:
     exit

பதிவேட்டில் என்ன இருக்கிறது என்பதை நினைவில் வைத்துக் கொண்டால் r1 நிரல் கர்னல் மற்றும் பதிவேட்டில் இருந்து சூழலுக்கு ஒரு சுட்டிக்காட்டி அனுப்பப்படுகிறது r0 மதிப்பு கர்னலுக்குத் திரும்பியது, பின்னர் சூழலுக்கான சுட்டிக்காட்டி பூஜ்ஜியமாக இருந்தால், நாம் 1 ஐத் திரும்பப் பெறுகிறோம், இல்லையெனில் - 2. மூலத்தைப் பார்த்து நாம் சரிதானா என்பதைச் சரிபார்ப்போம்:

$ cat readelf-example.c
int foo(void *ctx)
{
        return ctx ? 2 : 1;
}

ஆம், இது ஒரு அர்த்தமற்ற நிரல், ஆனால் இது நான்கு எளிய வழிமுறைகளாக மொழிபெயர்க்கப்பட்டுள்ளது.

விதிவிலக்கு எடுத்துக்காட்டு: 16-பைட் அறிவுறுத்தல்

சில அறிவுறுத்தல்கள் 64 பிட்களுக்கு மேல் எடுக்கும் என்று முன்னர் குறிப்பிட்டோம். எடுத்துக்காட்டாக, இது அறிவுறுத்தல்களுக்கு பொருந்தும் lddw (குறியீடு = 0x18 = BPF_LD | BPF_DW | BPF_IMM) - புலங்களில் இருந்து ஒரு இரட்டை வார்த்தையை பதிவேட்டில் ஏற்றவும் Imm. உண்மைதான் Imm 32 அளவு உள்ளது, மற்றும் இரட்டை வார்த்தை 64 பிட்கள், எனவே ஒரு 64-பிட் அறிவுறுத்தலில் 64-பிட் உடனடி மதிப்பை பதிவேட்டில் ஏற்றுவது வேலை செய்யாது. இதைச் செய்ய, 64-பிட் மதிப்பின் இரண்டாவது பகுதியை புலத்தில் சேமிக்க இரண்டு அருகிலுள்ள வழிமுறைகள் பயன்படுத்தப்படுகின்றன Imm. ஒரு எடுத்துக்காட்டு:

$ cat x64.c
long foo(void *ctx)
{
        return 0x11223344aabbccdd;
}
$ clang -target bpf -c x64.c -o x64.o -O2
$ llvm-readelf -x .text x64.o
Hex dump of section '.text':
0x00000000 18000000 ddccbbaa 00000000 44332211 ............D3".
0x00000010 95000000 00000000                   ........

பைனரி நிரலில் இரண்டு வழிமுறைகள் மட்டுமே உள்ளன:

Binary                                 Disassm
18000000 ddccbbaa 00000000 44332211    r0 = Imm[0]|Imm[1]
95000000 00000000                      exit

அறிவுறுத்தல்களுடன் மீண்டும் சந்திப்போம் lddw, இடமாற்றங்கள் மற்றும் வரைபடங்களுடன் வேலை செய்வது பற்றி பேசும்போது.

எடுத்துக்காட்டு: நிலையான கருவிகளைப் பயன்படுத்தி BPF பிரித்தெடுத்தல்

எனவே, நாங்கள் BPF பைனரி குறியீடுகளைப் படிக்கக் கற்றுக்கொண்டோம், தேவைப்பட்டால் எந்த அறிவுறுத்தலையும் அலசத் தயாராக இருக்கிறோம். இருப்பினும், நடைமுறையில் நிலையான கருவிகளைப் பயன்படுத்தி நிரல்களை பிரிப்பது மிகவும் வசதியானது மற்றும் வேகமானது என்று சொல்வது மதிப்பு, எடுத்துக்காட்டாக:

$ llvm-objdump -d x64.o

Disassembly of section .text:

0000000000000000 <foo>:
 0: 18 00 00 00 dd cc bb aa 00 00 00 00 44 33 22 11 r0 = 1234605617868164317 ll
 2: 95 00 00 00 00 00 00 00 exit

BPF பொருள்களின் வாழ்க்கைச் சுழற்சி, bpffs கோப்பு முறைமை

(இந்த துணைப்பிரிவில் விவரிக்கப்பட்டுள்ள சில விவரங்களை நான் முதலில் கற்றுக்கொண்டேன் அஞ்சல் அலெக்ஸி ஸ்டாரோவோய்டோவ் BPF வலைப்பதிவு.)

BPF பொருள்கள் - நிரல்கள் மற்றும் வரைபடங்கள் - கட்டளைகளைப் பயன்படுத்தி பயனர் இடத்திலிருந்து உருவாக்கப்படுகின்றன BPF_PROG_LOAD и BPF_MAP_CREATE அமைப்பு அழைப்பு bpf(2), இது எப்படி நடக்கிறது என்பது பற்றி அடுத்த பகுதியில் பேசுவோம். இது கர்னல் தரவு கட்டமைப்புகள் மற்றும் அவை ஒவ்வொன்றிற்கும் உருவாக்குகிறது refcount (குறிப்பு எண்ணிக்கை) ஒன்றுக்கு அமைக்கப்பட்டுள்ளது, மேலும் பொருளைச் சுட்டிக்காட்டும் கோப்பு விளக்கமானது பயனருக்குத் திரும்பும். கைப்பிடி மூடப்பட்ட பிறகு refcount பொருள் ஒன்று குறைக்கப்படுகிறது, அது பூஜ்ஜியத்தை அடையும் போது, ​​பொருள் அழிக்கப்படுகிறது.

நிரல் வரைபடங்களைப் பயன்படுத்தினால், பின்னர் refcount நிரலை ஏற்றிய பின் இந்த வரைபடங்கள் ஒன்று அதிகரிக்கப்படும், அதாவது. அவற்றின் கோப்பு விளக்கங்களை பயனர் செயல்முறையிலிருந்து மூடலாம் refcount பூஜ்யம் ஆகாது:

சிறியவர்களுக்கான BPF, பகுதி ஒன்று: நீட்டிக்கப்பட்ட BPF

ஒரு நிரலை வெற்றிகரமாக ஏற்றிய பிறகு, நாங்கள் வழக்கமாக அதை ஒருவித நிகழ்வு ஜெனரேட்டருடன் இணைப்போம். எடுத்துக்காட்டாக, உள்வரும் பாக்கெட்டுகளை செயலாக்க அல்லது சிலவற்றுடன் இணைக்க பிணைய இடைமுகத்தில் வைக்கலாம் tracepoint மையத்தில். இந்த கட்டத்தில், குறிப்பு கவுண்டரும் ஒன்று அதிகரிக்கும் மற்றும் ஏற்றி நிரலில் கோப்பு விளக்கத்தை மூட முடியும்.

இப்போது பூட்லோடரை மூடினால் என்ன நடக்கும்? இது நிகழ்வு ஜெனரேட்டர் (ஹூக்) வகையைப் பொறுத்தது. ஏற்றி முடிந்ததும் அனைத்து நெட்வொர்க் ஹூக்குகளும் இருக்கும், இவை உலகளாவிய கொக்கிகள் என்று அழைக்கப்படுகின்றன. மேலும், எடுத்துக்காட்டாக, ட்ரேஸ் புரோகிராம்களை உருவாக்கிய செயல்முறை முடிவடைந்த பிறகு வெளியிடப்படும் (எனவே அவை உள்ளூர் என்று அழைக்கப்படுகின்றன, "உள்ளூர் முதல் செயல்முறை வரை"). தொழில்நுட்ப ரீதியாக, உள்ளூர் கொக்கிகள் எப்போதும் பயனர் இடத்தில் தொடர்புடைய கோப்பு விளக்கத்தைக் கொண்டிருக்கும், எனவே செயல்முறை மூடப்படும்போது மூடப்படும், ஆனால் உலகளாவிய கொக்கிகள் இல்லை. பின்வரும் படத்தில், சிவப்பு சிலுவைகளைப் பயன்படுத்தி, உள்ளூர் மற்றும் உலகளாவிய கொக்கிகளின் விஷயத்தில், ஏற்றி நிரல் நிறுத்தப்படுவது பொருட்களின் வாழ்நாளை எவ்வாறு பாதிக்கிறது என்பதைக் காட்ட முயற்சிக்கிறேன்.

சிறியவர்களுக்கான BPF, பகுதி ஒன்று: நீட்டிக்கப்பட்ட BPF

உள்ளூர் மற்றும் உலகளாவிய கொக்கிகளுக்கு இடையில் ஏன் வேறுபாடு உள்ளது? சில வகையான பிணைய நிரல்களை இயக்குவது பயனர் இடம் இல்லாமல் அர்த்தமுள்ளதாக இருக்கும், எடுத்துக்காட்டாக, DDoS பாதுகாப்பை கற்பனை செய்து பாருங்கள் - துவக்க ஏற்றி விதிகளை எழுதி BPF நிரலை பிணைய இடைமுகத்துடன் இணைக்கிறது, அதன் பிறகு துவக்க ஏற்றி தன்னைத்தானே கொல்ல முடியும். மறுபுறம், பத்து நிமிடங்களில் நீங்கள் முழங்காலில் எழுதிய பிழைத்திருத்த ட்ரேஸ் திட்டத்தை கற்பனை செய்து பாருங்கள் - அது முடிந்ததும், கணினியில் குப்பைகள் எதுவும் இருக்கக்கூடாது என்று நீங்கள் விரும்புகிறீர்கள், மேலும் உள்ளூர் கொக்கிகள் அதை உறுதி செய்யும்.

மறுபுறம், நீங்கள் கர்னலில் உள்ள ஒரு ட்ரேஸ்பாயிண்டுடன் இணைக்க விரும்புகிறீர்கள் என்று கற்பனை செய்து, பல ஆண்டுகளாக புள்ளிவிவரங்களை சேகரிக்க வேண்டும். இந்த வழக்கில், நீங்கள் பயனர் பகுதியை முடிக்க வேண்டும் மற்றும் அவ்வப்போது புள்ளிவிவரங்களுக்கு திரும்ப வேண்டும். bpf கோப்பு முறைமை இந்த வாய்ப்பை வழங்குகிறது. இது நினைவகத்தில் மட்டுமே உள்ள போலி-கோப்பு அமைப்பாகும், இது BPF பொருட்களைக் குறிப்பிடும் கோப்புகளை உருவாக்க அனுமதிக்கிறது மற்றும் அதன் மூலம் அதிகரிக்கும் refcount பொருள்கள். இதற்குப் பிறகு, ஏற்றி வெளியேறலாம், மேலும் அது உருவாக்கிய பொருள்கள் உயிருடன் இருக்கும்.

சிறியவர்களுக்கான BPF, பகுதி ஒன்று: நீட்டிக்கப்பட்ட BPF

BPF பொருட்களைக் குறிப்பிடும் கோப்புகளை bpffs இல் உருவாக்குவது "பின்னிங்" என்று அழைக்கப்படுகிறது (பின்வரும் சொற்றொடரில் உள்ளது: "செயல்முறையானது BPF நிரல் அல்லது வரைபடத்தைப் பின் செய்யலாம்"). BPF பொருட்களுக்கான கோப்பு பொருட்களை உருவாக்குவது, உள்ளூர் பொருட்களின் ஆயுளை நீட்டிப்பதற்கு மட்டுமல்ல, உலகளாவிய பொருட்களின் பயன்பாட்டிற்கும் அர்த்தமுள்ளதாக இருக்கிறது - உலகளாவிய DDoS பாதுகாப்புத் திட்டத்தின் உதாரணத்திற்குச் செல்ல, நாங்கள் வந்து புள்ளிவிவரங்களைப் பார்க்க விரும்புகிறோம். அவ்வப்போது.

BPF கோப்பு முறைமை பொதுவாக ஏற்றப்படும் /sys/fs/bpf, ஆனால் இது உள்நாட்டிலும் ஏற்றப்படலாம், எடுத்துக்காட்டாக, இது போன்றது:

$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint

கோப்பு முறைமை பெயர்கள் கட்டளையைப் பயன்படுத்தி உருவாக்கப்படுகின்றன BPF_OBJ_PIN BPF அமைப்பு அழைப்பு. விளக்குவதற்கு, ஒரு நிரலை எடுத்து, அதை தொகுத்து, பதிவேற்றி, அதை பின் செய்வோம் bpffs. எங்கள் நிரல் பயனுள்ள எதையும் செய்யவில்லை, நாங்கள் குறியீட்டை மட்டுமே வழங்குகிறோம், எனவே நீங்கள் உதாரணத்தை மீண்டும் உருவாக்கலாம்:

$ cat test.c
__attribute__((section("xdp"), used))
int test(void *ctx)
{
        return 0;
}

char _license[] __attribute__((section("license"), used)) = "GPL";

இந்த நிரலைத் தொகுத்து, கோப்பு முறைமையின் உள்ளூர் நகலை உருவாக்குவோம் bpffs:

$ clang -target bpf -c test.c -o test.o
$ mkdir bpf-mountpoint
$ sudo mount -t bpf none bpf-mountpoint

இப்போது பயன்பாட்டைப் பயன்படுத்தி எங்கள் நிரலைப் பதிவிறக்குவோம் bpftool மற்றும் அதனுடன் உள்ள கணினி அழைப்புகளைப் பார்க்கவும் bpf(2) (ஸ்ட்ரேஸ் வெளியீட்டில் இருந்து சில பொருத்தமற்ற கோடுகள் அகற்றப்பட்டன):

$ sudo strace -e bpf bpftool prog load ./test.o bpf-mountpoint/test
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, prog_name="test", ...}, 120) = 3
bpf(BPF_OBJ_PIN, {pathname="bpf-mountpoint/test", bpf_fd=3}, 120) = 0

இதைப் பயன்படுத்தி நிரலை ஏற்றியுள்ளோம் BPF_PROG_LOAD, கர்னலில் இருந்து ஒரு கோப்பு விளக்கத்தைப் பெற்றது 3 மற்றும் கட்டளையைப் பயன்படுத்தி BPF_OBJ_PIN இந்த கோப்பு விளக்கத்தை ஒரு கோப்பாக பின் செய்தேன் "bpf-mountpoint/test". இதற்குப் பிறகு பூட்லோடர் நிரல் bpftool வேலை முடிந்தது, ஆனால் எங்கள் நிரல் கர்னலில் இருந்தது, இருப்பினும் நாங்கள் அதை எந்த பிணைய இடைமுகத்திலும் இணைக்கவில்லை:

$ sudo bpftool prog | tail -3
783: xdp  name test  tag 5c8ba0cf164cb46c  gpl
        loaded_at 2020-05-05T13:27:08+0000  uid 0
        xlated 24B  jited 41B  memlock 4096B

நாம் கோப்பு பொருளை சாதாரணமாக நீக்கலாம் unlink(2) அதன் பிறகு தொடர்புடைய நிரல் நீக்கப்படும்:

$ sudo rm ./bpf-mountpoint/test
$ sudo bpftool prog show id 783
Error: get by id (783): No such file or directory

பொருட்களை நீக்குதல்

பொருட்களை நீக்குவது பற்றி பேசுகையில், நிரலை ஹூக்கிலிருந்து (நிகழ்வு ஜெனரேட்டர்) துண்டித்த பிறகு, ஒரு புதிய நிகழ்வு கூட அதன் துவக்கத்தைத் தூண்டாது என்பதை தெளிவுபடுத்துவது அவசியம், இருப்பினும், நிரலின் அனைத்து நிகழ்வுகளும் சாதாரண வரிசையில் முடிக்கப்படும். .

சில வகையான BPF நிரல்கள் உங்களை விமானத்தில் நிரலை மாற்ற அனுமதிக்கின்றன, அதாவது. வரிசை அணுவை வழங்குகின்றன replace = detach old program, attach new program. இந்த வழக்கில், நிரலின் பழைய பதிப்பின் அனைத்து செயலில் உள்ள நிகழ்வுகளும் அவற்றின் வேலையை முடிக்கும், மேலும் புதிய நிரலிலிருந்து புதிய நிகழ்வு கையாளுபவர்கள் உருவாக்கப்படுவார்கள், மேலும் இங்கு “அணுசக்தி” என்பது ஒரு நிகழ்வையும் தவறவிடாது என்பதாகும்.

நிகழ்வு ஆதாரங்களுடன் நிரல்களை இணைத்தல்

இந்த கட்டுரையில், நிகழ்வு மூலங்களுடன் நிரல்களை இணைப்பதை நாங்கள் தனித்தனியாக விவரிக்க மாட்டோம், ஏனெனில் ஒரு குறிப்பிட்ட வகை நிரலின் சூழலில் இதைப் படிப்பது அர்த்தமுள்ளதாக இருக்கும். செ.மீ. உதாரணமாக கீழே, XDP போன்ற நிரல்கள் எவ்வாறு இணைக்கப்பட்டுள்ளன என்பதைக் காட்டுகிறோம்.

பிபிஎஃப் சிஸ்டம் அழைப்பைப் பயன்படுத்தி பொருட்களைக் கையாளுதல்

BPF திட்டங்கள்

அனைத்து BPF பொருள்களும் கணினி அழைப்பைப் பயன்படுத்தி பயனர் இடத்திலிருந்து உருவாக்கப்பட்டு நிர்வகிக்கப்படுகின்றன bpf, பின்வரும் முன்மாதிரி உள்ளது:

#include <linux/bpf.h>

int bpf(int cmd, union bpf_attr *attr, unsigned int size);

இதோ அணி cmd வகையின் மதிப்புகளில் ஒன்றாகும் enum bpf_cmd, attr - ஒரு குறிப்பிட்ட நிரலுக்கான அளவுருக்களுக்கான சுட்டி மற்றும் size - சுட்டிக்காட்டி படி பொருள் அளவு, அதாவது. பொதுவாக இது sizeof(*attr). கர்னல் 5.8 இல் கணினி அழைப்பு bpf 34 வெவ்வேறு கட்டளைகளை ஆதரிக்கிறது, மற்றும் உறுதிப்பாடு union bpf_attr 200 வரிகளை ஆக்கிரமித்துள்ளது. ஆனால் இதைப் பற்றி நாம் பயப்படக்கூடாது, ஏனென்றால் பல கட்டுரைகளின் போது கட்டளைகள் மற்றும் அளவுருக்களுடன் நம்மைப் பழக்கப்படுத்துவோம்.

அணியுடன் தொடங்குவோம் BPF_PROG_LOAD, இது BPF நிரல்களை உருவாக்குகிறது - BPF வழிமுறைகளின் தொகுப்பை எடுத்து கர்னலில் ஏற்றுகிறது. ஏற்றும் தருணத்தில், சரிபார்ப்பு தொடங்கப்பட்டது, பின்னர் JIT கம்பைலர் மற்றும், வெற்றிகரமான செயல்பாட்டிற்குப் பிறகு, நிரல் கோப்பு விளக்கமானது பயனருக்குத் திரும்பும். அவருக்கு அடுத்து என்ன நடக்கிறது என்பதை முந்தைய பகுதியில் பார்த்தோம் BPF பொருள்களின் வாழ்க்கைச் சுழற்சி பற்றி.

எளிமையான BPF நிரலை ஏற்றும் தனிப்பயன் நிரலை இப்போது எழுதுவோம், ஆனால் முதலில் நாம் எந்த வகையான நிரலை ஏற்ற வேண்டும் என்பதைத் தீர்மானிக்க வேண்டும் - நாம் தேர்ந்தெடுக்க வேண்டும் வகை இந்த வகையின் கட்டமைப்பிற்குள், சரிபார்ப்பு தேர்வில் தேர்ச்சி பெறும் ஒரு நிரலை எழுதவும். இருப்பினும், செயல்முறையை சிக்கலாக்காமல் இருக்க, இங்கே ஒரு ஆயத்த தீர்வு உள்ளது: நாங்கள் ஒரு திட்டத்தை எடுப்போம் BPF_PROG_TYPE_XDP, இது மதிப்பை வழங்கும் XDP_PASS (அனைத்து தொகுப்புகளையும் தவிர்க்கவும்). BPF அசெம்பிளரில் இது மிகவும் எளிமையாகத் தெரிகிறது:

r0 = 2
exit

நாங்கள் முடிவு செய்த பிறகு என்று நாங்கள் பதிவேற்றுவோம், அதை எப்படிச் செய்வோம் என்பதை நாங்கள் உங்களுக்குச் சொல்லலாம்:

#define _GNU_SOURCE
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/bpf.h>

static inline __u64 ptr_to_u64(const void *ptr)
{
        return (__u64) (unsigned long) ptr;
}

int main(void)
{
    struct bpf_insn insns[] = {
        {
            .code = BPF_ALU64 | BPF_MOV | BPF_K,
            .dst_reg = BPF_REG_0,
            .imm = XDP_PASS
        },
        {
            .code = BPF_JMP | BPF_EXIT
        },
    };

    union bpf_attr attr = {
        .prog_type = BPF_PROG_TYPE_XDP,
        .insns     = ptr_to_u64(insns),
        .insn_cnt  = sizeof(insns)/sizeof(insns[0]),
        .license   = ptr_to_u64("GPL"),
    };

    strncpy(attr.prog_name, "woo", sizeof(attr.prog_name));
    syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));

    for ( ;; )
        pause();
}

ஒரு நிரலில் சுவாரஸ்யமான நிகழ்வுகள் ஒரு வரிசையின் வரையறையுடன் தொடங்குகின்றன insns - இயந்திரக் குறியீட்டில் எங்கள் BPF திட்டம். இந்த வழக்கில், BPF திட்டத்தின் ஒவ்வொரு அறிவுறுத்தலும் கட்டமைப்பில் நிரம்பியுள்ளது bpf_insn. முதல் உறுப்பு insns அறிவுறுத்தல்களுடன் இணங்குகிறது r0 = 2, இரண்டாவது - exit.

பின்வாங்கவும். இயந்திரக் குறியீடுகளை எழுதுவதற்கும், கர்னல் தலைப்புக் கோப்பைப் பயன்படுத்துவதற்கும் மிகவும் வசதியான மேக்ரோக்களை கர்னல் வரையறுக்கிறது. tools/include/linux/filter.h நாம் எழுத முடியும்

struct bpf_insn insns[] = {
    BPF_MOV64_IMM(BPF_REG_0, XDP_PASS),
    BPF_EXIT_INSN()
};

ஆனால் சொந்தக் குறியீட்டில் BPF நிரல்களை எழுதுவது கர்னலில் சோதனைகள் மற்றும் BPF பற்றிய கட்டுரைகளை எழுதுவதற்கு மட்டுமே அவசியம் என்பதால், இந்த மேக்ரோக்கள் இல்லாதது உண்மையில் டெவலப்பரின் வாழ்க்கையை சிக்கலாக்காது.

BPF நிரலை வரையறுத்த பிறகு, அதை கர்னலில் ஏற்றுவதற்குச் செல்கிறோம். எங்களின் குறைந்தபட்ச அளவுருக்கள் attr நிரல் வகை, தொகுப்பு மற்றும் வழிமுறைகளின் எண்ணிக்கை, தேவையான உரிமம் மற்றும் பெயர் ஆகியவை அடங்கும் "woo", பதிவிறக்கிய பிறகு கணினியில் எங்கள் நிரலைக் கண்டறிய இதைப் பயன்படுத்துகிறோம். நிரல், வாக்குறுதியளித்தபடி, கணினி அழைப்பைப் பயன்படுத்தி கணினியில் ஏற்றப்படுகிறது bpf.

நிரலின் முடிவில், பேலோடை உருவகப்படுத்தும் எல்லையற்ற சுழற்சியில் நாம் முடிவடைகிறோம். இது இல்லாமல், கணினி அழைப்பு எங்களுக்குத் திரும்பிய கோப்பு விளக்கத்தை மூடும்போது நிரல் கர்னலால் அழிக்கப்படும். bpf, மற்றும் நாங்கள் அதை கணினியில் பார்க்க மாட்டோம்.

சரி, நாங்கள் சோதனைக்கு தயாராக இருக்கிறோம். கீழ் நிரலை அசெம்பிள் செய்து இயக்கலாம் straceஎல்லாம் சரியாகச் செயல்படுகிறதா என்பதைச் சரிபார்க்க:

$ clang -g -O2 simple-prog.c -o simple-prog

$ sudo strace ./simple-prog
execve("./simple-prog", ["./simple-prog"], 0x7ffc7b553480 /* 13 vars */) = 0
...
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, insn_cnt=2, insns=0x7ffe03c4ed50, license="GPL", log_level=0, log_size=0, log_buf=NULL, kern_version=KERNEL_V
ERSION(0, 0, 0), prog_flags=0, prog_name="woo", prog_ifindex=0, expected_attach_type=BPF_CGROUP_INET_INGRESS}, 72) = 3
pause(

எல்லாம் நன்றாக இருக்கிறது, bpf(2) கைப்பிடி 3 ஐ எங்களிடம் திருப்பி அனுப்பினோம், நாங்கள் ஒரு எல்லையற்ற வளையத்திற்குள் சென்றோம் pause(). கணினியில் எங்கள் நிரலைக் கண்டுபிடிக்க முயற்சிப்போம். இதைச் செய்ய, நாங்கள் மற்றொரு முனையத்திற்குச் சென்று பயன்பாட்டைப் பயன்படுத்துவோம் bpftool:

# bpftool prog | grep -A3 woo
390: xdp  name woo  tag 3b185187f1855c4c  gpl
        loaded_at 2020-08-31T24:66:44+0000  uid 0
        xlated 16B  jited 40B  memlock 4096B
        pids simple-prog(10381)

கணினியில் ஏற்றப்பட்ட நிரல் இருப்பதைக் காண்கிறோம் woo அதன் உலகளாவிய ஐடி 390 மற்றும் தற்போது செயலில் உள்ளது simple-prog நிரலை சுட்டிக்காட்டும் திறந்த கோப்பு விளக்கம் உள்ளது (மற்றும் இருந்தால் simple-prog பிறகு வேலையை முடிப்பார் woo மறைந்துவிடும்). எதிர்பார்த்தபடி, நிரல் woo BPF கட்டமைப்பில் பைனரி குறியீடுகளின் 16 பைட்டுகள் - இரண்டு வழிமுறைகள் - ஆனால் அதன் சொந்த வடிவத்தில் (x86_64) ஏற்கனவே 40 பைட்டுகள் ஆகும். எங்கள் திட்டத்தை அதன் அசல் வடிவத்தில் பார்க்கலாம்:

# bpftool prog dump xlated id 390
   0: (b7) r0 = 2
   1: (95) exit

ஆச்சரியங்கள் இல்லை. இப்போது JIT கம்பைலரால் உருவாக்கப்பட்ட குறியீட்டைப் பார்ப்போம்:

# bpftool prog dump jited id 390
bpf_prog_3b185187f1855c4c_woo:
   0:   nopl   0x0(%rax,%rax,1)
   5:   push   %rbp
   6:   mov    %rsp,%rbp
   9:   sub    $0x0,%rsp
  10:   push   %rbx
  11:   push   %r13
  13:   push   %r14
  15:   push   %r15
  17:   pushq  $0x0
  19:   mov    $0x2,%eax
  1e:   pop    %rbx
  1f:   pop    %r15
  21:   pop    %r14
  23:   pop    %r13
  25:   pop    %rbx
  26:   leaveq
  27:   retq

மிகவும் பயனுள்ளதாக இல்லை exit(2), ஆனால் நியாயமாக, எங்கள் நிரல் மிகவும் எளிமையானது, மேலும் அற்பமான நிரல்களுக்கு JIT தொகுப்பாளரால் சேர்க்கப்பட்ட முன்னுரை மற்றும் எபிலோக் தேவை.

வரைபடங்கள்

BPF நிரல்கள் மற்ற BPF நிரல்களுக்கும் பயனர் இடத்தில் உள்ள நிரல்களுக்கும் அணுகக்கூடிய கட்டமைக்கப்பட்ட நினைவகப் பகுதிகளைப் பயன்படுத்தலாம். இந்த பொருள்கள் வரைபடங்கள் என்று அழைக்கப்படுகின்றன, மேலும் இந்த பிரிவில் கணினி அழைப்பைப் பயன்படுத்தி அவற்றை எவ்வாறு கையாள்வது என்பதைக் காண்பிப்போம் bpf.

வரைபடங்களின் திறன்கள் பகிரப்பட்ட நினைவகத்தை அணுகுவதற்கு மட்டும் மட்டுப்படுத்தப்படவில்லை என்று இப்போதே சொல்லலாம். எடுத்துக்காட்டாக, BPF நிரல்களுக்கான சுட்டிகள் அல்லது பிணைய இடைமுகங்களுக்கான சுட்டிகள், perf நிகழ்வுகளுடன் பணிபுரிவதற்கான வரைபடங்கள் போன்றவற்றைக் கொண்ட சிறப்பு நோக்க வரைபடங்கள் உள்ளன. அவற்றைப் பற்றி இங்கு பேச மாட்டோம், அதனால் வாசகரை குழப்ப வேண்டாம். இது தவிர, ஒத்திசைவு சிக்கல்களை நாங்கள் புறக்கணிக்கிறோம், ஏனெனில் இது எங்கள் எடுத்துக்காட்டுகளுக்கு முக்கியமில்லை. கிடைக்கக்கூடிய வரைபட வகைகளின் முழுமையான பட்டியலைக் காணலாம் <linux/bpf.h>, மற்றும் இந்த பிரிவில் வரலாற்று ரீதியாக முதல் வகை ஹாஷ் அட்டவணையை உதாரணமாக எடுத்துக்கொள்வோம் BPF_MAP_TYPE_HASH.

நீங்கள் ஒரு ஹாஷ் அட்டவணையை உருவாக்கினால், C++ என்று சொல்லுங்கள், நீங்கள் சொல்வீர்கள் unordered_map<int,long> woo, இது ரஷ்ய மொழியில் "எனக்கு ஒரு அட்டவணை தேவை woo வரம்பற்ற அளவு, அதன் விசைகள் வகை int, மற்றும் மதிப்புகள் வகை long" ஒரு BPF ஹாஷ் அட்டவணையை உருவாக்க, நாம் அட்டவணையின் அதிகபட்ச அளவைக் குறிப்பிடுவதைத் தவிர, அதையே செய்ய வேண்டும், மேலும் விசைகள் மற்றும் மதிப்புகளின் வகைகளைக் குறிப்பிடுவதற்குப் பதிலாக, அவற்றின் அளவுகளை பைட்டுகளில் குறிப்பிட வேண்டும். . வரைபடங்களை உருவாக்க, கட்டளையைப் பயன்படுத்தவும் BPF_MAP_CREATE அமைப்பு அழைப்பு bpf. வரைபடத்தை உருவாக்கும் அதிகமாகவோ அல்லது குறைவாகவோ குறைந்தபட்ச நிரலைப் பார்ப்போம். BPF நிரல்களை ஏற்றும் முந்தைய நிரலுக்குப் பிறகு, இது உங்களுக்கு எளிமையானதாகத் தோன்றும்:

$ cat simple-map.c
#define _GNU_SOURCE
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/bpf.h>

int main(void)
{
    union bpf_attr attr = {
        .map_type = BPF_MAP_TYPE_HASH,
        .key_size = sizeof(int),
        .value_size = sizeof(int),
        .max_entries = 4,
    };
    strncpy(attr.map_name, "woo", sizeof(attr.map_name));
    syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));

    for ( ;; )
        pause();
}

இங்கே நாம் அளவுருக்களின் தொகுப்பை வரையறுக்கிறோம் attr, அதில் நாம் "எனக்கு விசைகள் மற்றும் அளவு மதிப்புகள் கொண்ட ஹாஷ் அட்டவணை தேவை sizeof(int), இதில் நான் அதிகபட்சம் நான்கு உறுப்புகளை வைக்க முடியும்." BPF வரைபடங்களை உருவாக்கும் போது, ​​நீங்கள் மற்ற அளவுருக்களைக் குறிப்பிடலாம், எடுத்துக்காட்டாக, நிரலுடன் உள்ள எடுத்துக்காட்டில் உள்ளதைப் போலவே, பொருளின் பெயரைக் குறிப்பிடுகிறோம் "woo".

நிரலை தொகுத்து இயக்குவோம்:

$ clang -g -O2 simple-map.c -o simple-map
$ sudo strace ./simple-map
execve("./simple-map", ["./simple-map"], 0x7ffd40a27070 /* 14 vars */) = 0
...
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_HASH, key_size=4, value_size=4, max_entries=4, map_name="woo", ...}, 72) = 3
pause(

இதோ சிஸ்டம் கால் bpf(2) விளக்க வரைபட எண்ணை எங்களுக்குத் திருப்பித் தந்தார் 3 பின்னர் நிரல், எதிர்பார்த்தபடி, கணினி அழைப்பில் கூடுதல் வழிமுறைகளுக்காக காத்திருக்கிறது pause(2).

இப்போது எங்கள் நிரலை பின்னணிக்கு அனுப்புவோம் அல்லது மற்றொரு முனையத்தைத் திறந்து பயன்பாட்டைப் பயன்படுத்தி எங்கள் பொருளைப் பார்ப்போம் bpftool (எங்கள் வரைபடத்தை அதன் பெயரால் மற்றவர்களிடமிருந்து வேறுபடுத்தி அறியலாம்):

$ sudo bpftool map
...
114: hash  name woo  flags 0x0
        key 4B  value 4B  max_entries 4  memlock 4096B
...

எண் 114 என்பது நமது பொருளின் உலகளாவிய அடையாளமாகும். கணினியில் உள்ள எந்த நிரலும் கட்டளையைப் பயன்படுத்தி ஏற்கனவே உள்ள வரைபடத்தைத் திறக்க இந்த ஐடியைப் பயன்படுத்தலாம் BPF_MAP_GET_FD_BY_ID அமைப்பு அழைப்பு bpf.

இப்போது நாம் நமது ஹாஷ் அட்டவணையுடன் விளையாடலாம். அதன் உள்ளடக்கங்களைப் பார்ப்போம்:

$ sudo bpftool map dump id 114
Found 0 elements

காலியாக. அதற்கு ஒரு மதிப்பை வைப்போம் hash[1] = 1:

$ sudo bpftool map update id 114 key 1 0 0 0 value 1 0 0 0

மீண்டும் அட்டவணையைப் பார்ப்போம்:

$ sudo bpftool map dump id 114
key: 01 00 00 00  value: 01 00 00 00
Found 1 element

ஹூரே! ஒரு உறுப்பைச் சேர்க்க முடிந்தது. இதை செய்ய நாம் பைட் மட்டத்தில் வேலை செய்ய வேண்டும் என்பதை நினைவில் கொள்ளவும் bptftool ஹாஷ் அட்டவணையில் உள்ள மதிப்புகள் என்ன வகை என்று தெரியவில்லை. (இந்த அறிவை BTF ஐப் பயன்படுத்தி அவளுக்கு மாற்றலாம், ஆனால் இப்போது அதைப் பற்றி மேலும்.)

Bpftool எவ்வாறு சரியாகப் படித்து உறுப்புகளைச் சேர்க்கிறது? பேட்டைக்குக் கீழே பார்ப்போம்:

$ sudo strace -e bpf bpftool map dump id 114
bpf(BPF_MAP_GET_FD_BY_ID, {map_id=114, next_id=0, open_flags=0}, 120) = 3
bpf(BPF_MAP_GET_NEXT_KEY, {map_fd=3, key=NULL, next_key=0x55856ab65280}, 120) = 0
bpf(BPF_MAP_LOOKUP_ELEM, {map_fd=3, key=0x55856ab65280, value=0x55856ab652a0}, 120) = 0
key: 01 00 00 00  value: 01 00 00 00
bpf(BPF_MAP_GET_NEXT_KEY, {map_fd=3, key=0x55856ab65280, next_key=0x55856ab65280}, 120) = -1 ENOENT

முதலில் கட்டளையைப் பயன்படுத்தி அதன் உலகளாவிய ஐடி மூலம் வரைபடத்தைத் திறந்தோம் BPF_MAP_GET_FD_BY_ID и bpf(2) டிஸ்கிரிப்டர் 3 ஐ எங்களிடம் கொடுத்தது BPF_MAP_GET_NEXT_KEY கடந்து செல்வதன் மூலம் அட்டவணையில் முதல் விசையைக் கண்டோம் NULL "முந்தைய" விசைக்கு ஒரு சுட்டியாக. சாவி இருந்தால் நம்மால் முடியும் BPF_MAP_LOOKUP_ELEMஇது ஒரு மதிப்பை சுட்டிக்காட்டிக்கு வழங்கும் value. அடுத்த கட்டம், தற்போதைய விசைக்கு ஒரு சுட்டியை அனுப்புவதன் மூலம் அடுத்த உறுப்பைக் கண்டுபிடிக்க முயற்சிக்கிறோம், ஆனால் எங்கள் அட்டவணையில் ஒரு உறுப்பு மற்றும் கட்டளை மட்டுமே உள்ளது. BPF_MAP_GET_NEXT_KEY திரும்புகிறது ENOENT.

சரி, விசை 1 மூலம் மதிப்பை மாற்றுவோம், எங்கள் வணிக தர்க்கத்திற்கு பதிவு செய்ய வேண்டும் என்று வைத்துக்கொள்வோம் hash[1] = 2:

$ sudo strace -e bpf bpftool map update id 114 key 1 0 0 0 value 2 0 0 0
bpf(BPF_MAP_GET_FD_BY_ID, {map_id=114, next_id=0, open_flags=0}, 120) = 3
bpf(BPF_MAP_UPDATE_ELEM, {map_fd=3, key=0x55dcd72be260, value=0x55dcd72be280, flags=BPF_ANY}, 120) = 0

எதிர்பார்த்தபடி, இது மிகவும் எளிது: கட்டளை BPF_MAP_GET_FD_BY_ID ஐடி மற்றும் கட்டளை மூலம் எங்கள் வரைபடத்தைத் திறக்கிறது BPF_MAP_UPDATE_ELEM உறுப்பு மேலெழுதுகிறது.

எனவே, ஒரு நிரலில் இருந்து ஹாஷ் அட்டவணையை உருவாக்கிய பிறகு, அதன் உள்ளடக்கங்களை மற்றொரு நிரலில் இருந்து படிக்கலாம் மற்றும் எழுதலாம். கட்டளை வரியிலிருந்து இதைச் செய்ய முடிந்தால், கணினியில் உள்ள வேறு எந்த நிரலும் இதைச் செய்ய முடியும் என்பதை நினைவில் கொள்க. மேலே விவரிக்கப்பட்ட கட்டளைகளுக்கு கூடுதலாக, பயனர் இடத்திலிருந்து வரைபடங்களுடன் பணிபுரிய, பின்வரும்:

  • BPF_MAP_LOOKUP_ELEM: விசை மூலம் மதிப்பைக் கண்டறியவும்
  • BPF_MAP_UPDATE_ELEM: புதுப்பித்தல்/மதிப்பை உருவாக்குதல்
  • BPF_MAP_DELETE_ELEM: விசையை அகற்று
  • BPF_MAP_GET_NEXT_KEY: அடுத்த (அல்லது முதல்) விசையைக் கண்டறியவும்
  • BPF_MAP_GET_NEXT_ID: ஏற்கனவே உள்ள அனைத்து வரைபடங்களையும் பார்க்க உங்களை அனுமதிக்கிறது, அது எப்படி வேலை செய்கிறது bpftool map
  • BPF_MAP_GET_FD_BY_ID: ஏற்கனவே உள்ள வரைபடத்தை அதன் உலகளாவிய ஐடி மூலம் திறக்கவும்
  • BPF_MAP_LOOKUP_AND_DELETE_ELEM: ஒரு பொருளின் மதிப்பை அணுவாகப் புதுப்பித்து பழையதைத் திருப்பித் தரவும்
  • BPF_MAP_FREEZE: பயனர்வெளியில் இருந்து வரைபடத்தை மாற்ற முடியாததாக மாற்றவும் (இந்தச் செயல்பாட்டைச் செயல்தவிர்க்க முடியாது)
  • BPF_MAP_LOOKUP_BATCH, BPF_MAP_LOOKUP_AND_DELETE_BATCH, BPF_MAP_UPDATE_BATCH, BPF_MAP_DELETE_BATCH: வெகுஜன நடவடிக்கைகள். உதாரணத்திற்கு, BPF_MAP_LOOKUP_AND_DELETE_BATCH - வரைபடத்திலிருந்து அனைத்து மதிப்புகளையும் படித்து மீட்டமைப்பதற்கான ஒரே நம்பகமான வழி இதுதான்

இந்த கட்டளைகள் அனைத்தும் அனைத்து வரைபட வகைகளுக்கும் வேலை செய்யாது, ஆனால் பொதுவாக பயனர் இடத்திலிருந்து மற்ற வகை வரைபடங்களுடன் பணிபுரிவது ஹாஷ் அட்டவணைகளுடன் வேலை செய்வது போலவே இருக்கும்.

ஆர்டரின் பொருட்டு, ஹாஷ் அட்டவணை சோதனைகளை முடிப்போம். நான்கு விசைகள் வரை உள்ள அட்டவணையை நாங்கள் உருவாக்கியுள்ளோம் என்பதை நினைவில் கொள்க? இன்னும் சில கூறுகளைச் சேர்ப்போம்:

$ sudo bpftool map update id 114 key 2 0 0 0 value 1 0 0 0
$ sudo bpftool map update id 114 key 3 0 0 0 value 1 0 0 0
$ sudo bpftool map update id 114 key 4 0 0 0 value 1 0 0 0

இதுவரை மிகவும் நல்ல:

$ sudo bpftool map dump id 114
key: 01 00 00 00  value: 01 00 00 00
key: 02 00 00 00  value: 01 00 00 00
key: 04 00 00 00  value: 01 00 00 00
key: 03 00 00 00  value: 01 00 00 00
Found 4 elements

மேலும் ஒன்றைச் சேர்க்க முயற்சிப்போம்:

$ sudo bpftool map update id 114 key 5 0 0 0 value 1 0 0 0
Error: update failed: Argument list too long

எதிர்பார்த்தபடி, நாங்கள் வெற்றிபெறவில்லை. பிழையை இன்னும் விரிவாகப் பார்ப்போம்:

$ sudo strace -e bpf bpftool map update id 114 key 5 0 0 0 value 1 0 0 0
bpf(BPF_MAP_GET_FD_BY_ID, {map_id=114, next_id=0, open_flags=0}, 120) = 3
bpf(BPF_OBJ_GET_INFO_BY_FD, {info={bpf_fd=3, info_len=80, info=0x7ffe6c626da0}}, 120) = 0
bpf(BPF_MAP_UPDATE_ELEM, {map_fd=3, key=0x56049ded5260, value=0x56049ded5280, flags=BPF_ANY}, 120) = -1 E2BIG (Argument list too long)
Error: update failed: Argument list too long
+++ exited with 255 +++

எல்லாம் நன்றாக இருக்கிறது: எதிர்பார்த்தபடி, அணி BPF_MAP_UPDATE_ELEM புதிய, ஐந்தாவது, விசையை உருவாக்க முயற்சிக்கிறது, ஆனால் செயலிழக்கிறது E2BIG.

எனவே, நாம் BPF நிரல்களை உருவாக்கலாம் மற்றும் ஏற்றலாம், அத்துடன் பயனர் இடத்திலிருந்து வரைபடங்களை உருவாக்கி நிர்வகிக்கலாம். இப்போது BPF நிரல்களிலிருந்தே வரைபடங்களை எவ்வாறு பயன்படுத்தலாம் என்பதைப் பார்ப்பது தர்க்கரீதியானது. மெஷின் மேக்ரோ குறியீடுகளில் படிக்க கடினமாக இருக்கும் நிரல்களின் மொழியில் இதைப் பற்றி பேசலாம், ஆனால் உண்மையில் BPF திட்டங்கள் எவ்வாறு எழுதப்பட்டு பராமரிக்கப்படுகின்றன என்பதைக் காண்பிக்கும் நேரம் வந்துவிட்டது. libbpf.

(குறைந்த அளவிலான உதாரணம் இல்லாததால் அதிருப்தி அடையும் வாசகர்களுக்கு: வரைபடங்கள் மற்றும் உதவி செயல்பாடுகளைப் பயன்படுத்தி உருவாக்கப்பட்ட விரிவான நிரல்களை நாங்கள் பகுப்பாய்வு செய்வோம். libbpf மற்றும் அறிவுறுத்தல் மட்டத்தில் என்ன நடக்கிறது என்று சொல்லுங்கள். அதிருப்தியில் இருக்கும் வாசகர்களுக்கு மிகவும், நாங்கள் சேர்த்தோம் உதாரணமாக கட்டுரையில் பொருத்தமான இடத்தில்.)

libbpf ஐப் பயன்படுத்தி BPF நிரல்களை எழுதுதல்

இயந்திரக் குறியீடுகளைப் பயன்படுத்தி BPF நிரல்களை எழுதுவது முதல் முறையாக மட்டுமே சுவாரஸ்யமாக இருக்கும், பின்னர் மனநிறைவு ஏற்படுகிறது. இந்த நேரத்தில் நீங்கள் உங்கள் கவனத்தை திருப்ப வேண்டும் llvm, இது BPF கட்டமைப்பிற்கான குறியீட்டை உருவாக்குவதற்கான பின்தளம் மற்றும் ஒரு நூலகத்தைக் கொண்டுள்ளது libbpf, இது BPF பயன்பாடுகளின் பயனர் பக்கத்தை எழுதவும், பயன்படுத்தி உருவாக்கப்பட்ட BPF நிரல்களின் குறியீட்டை ஏற்றவும் உங்களை அனுமதிக்கிறது. llvm/clang.

உண்மையில், இந்த மற்றும் அடுத்தடுத்த கட்டுரைகளில் நாம் பார்ப்போம், libbpf அது இல்லாமல் நிறைய வேலை செய்கிறது (அல்லது ஒத்த கருவிகள் - iproute2, libbcc, libbpf-go, முதலியன) வாழ இயலாது. திட்டத்தின் கொலையாளி அம்சங்களில் ஒன்று libbpf என்பது BPF CO-RE (ஒருமுறை தொகுக்கவும், எல்லா இடங்களிலும் இயக்கவும்) - வெவ்வேறு APIகளில் இயங்கும் திறன் கொண்ட BPF நிரல்களை ஒரு கர்னலில் இருந்து மற்றொரு கர்னலுக்கு எடுத்துச் செல்ல உங்களை அனுமதிக்கும் திட்டம் (உதாரணமாக, பதிப்பிலிருந்து கர்னல் அமைப்பு மாறும்போது பதிப்புக்கு). CO-RE உடன் பணிபுரிய, உங்கள் கர்னல் BTF ஆதரவுடன் தொகுக்கப்பட வேண்டும் (இதை எப்படி செய்வது என்று பிரிவில் விவரிக்கிறோம் மேம்பாட்டு கருவிகள். உங்கள் கர்னல் BTF உடன் கட்டப்பட்டுள்ளதா அல்லது மிகவும் எளிமையாக இல்லை என்பதை நீங்கள் சரிபார்க்கலாம் - பின்வரும் கோப்பின் முன்னிலையில்:

$ ls -lh /sys/kernel/btf/vmlinux
-r--r--r-- 1 root root 2.6M Jul 29 15:30 /sys/kernel/btf/vmlinux

இந்த கோப்பு கர்னலில் பயன்படுத்தப்படும் அனைத்து தரவு வகைகளைப் பற்றிய தகவலையும் சேமித்து வைக்கிறது மற்றும் எங்கள் எல்லா எடுத்துக்காட்டுகளிலும் பயன்படுத்தப்படுகிறது libbpf. அடுத்த கட்டுரையில் CO-RE பற்றி விரிவாகப் பேசுவோம், ஆனால் இதில் - நீங்களே ஒரு கர்னலை உருவாக்குங்கள் CONFIG_DEBUG_INFO_BTF.

நூலகம் libbpf கோப்பகத்தில் சரியாக வாழ்கிறது tools/lib/bpf கர்னல் மற்றும் அதன் மேம்பாடு அஞ்சல் பட்டியல் மூலம் மேற்கொள்ளப்படுகிறது [email protected]. இருப்பினும், கர்னலுக்கு வெளியே வாழும் பயன்பாடுகளின் தேவைகளுக்காக ஒரு தனி களஞ்சியம் பராமரிக்கப்படுகிறது https://github.com/libbpf/libbpf இதில் கர்னல் நூலகம் அதிகமாகவோ அல்லது குறைவாகவோ படிக்கும் அணுகலுக்காக பிரதிபலிக்கிறது.

இந்தப் பகுதியில், நீங்கள் பயன்படுத்தும் திட்டத்தை எவ்வாறு உருவாக்கலாம் என்பதைப் பார்ப்போம் libbpf, பல (அதிகமாகவோ அல்லது குறைவாகவோ அர்த்தமற்ற) சோதனை நிரல்களை எழுதுவோம் மற்றும் அது எவ்வாறு செயல்படுகிறது என்பதை விரிவாக பகுப்பாய்வு செய்வோம். வரைபடங்கள், கர்னல் உதவியாளர்கள், BTF போன்றவற்றுடன் BPF நிரல்கள் எவ்வாறு தொடர்பு கொள்கின்றன என்பதை பின்வரும் பிரிவுகளில் மிக எளிதாக விளக்க இது அனுமதிக்கும்.

பொதுவாக திட்டங்கள் பயன்படுத்தி libbpf GitHub களஞ்சியத்தை ஒரு git துணைத் தொகுதியாகச் சேர்க்கவும், நாங்கள் அதையே செய்வோம்:

$ mkdir /tmp/libbpf-example
$ cd /tmp/libbpf-example/
$ git init-db
Initialized empty Git repository in /tmp/libbpf-example/.git/
$ git submodule add https://github.com/libbpf/libbpf.git
Cloning into '/tmp/libbpf-example/libbpf'...
remote: Enumerating objects: 200, done.
remote: Counting objects: 100% (200/200), done.
remote: Compressing objects: 100% (103/103), done.
remote: Total 3354 (delta 101), reused 118 (delta 79), pack-reused 3154
Receiving objects: 100% (3354/3354), 2.05 MiB | 10.22 MiB/s, done.
Resolving deltas: 100% (2176/2176), done.

போகிறேன் libbpf மிகவும் எளிமையான:

$ cd libbpf/src
$ mkdir build
$ OBJDIR=build DESTDIR=root make -s install
$ find root
root
root/usr
root/usr/include
root/usr/include/bpf
root/usr/include/bpf/bpf_tracing.h
root/usr/include/bpf/xsk.h
root/usr/include/bpf/libbpf_common.h
root/usr/include/bpf/bpf_endian.h
root/usr/include/bpf/bpf_helpers.h
root/usr/include/bpf/btf.h
root/usr/include/bpf/bpf_helper_defs.h
root/usr/include/bpf/bpf.h
root/usr/include/bpf/libbpf_util.h
root/usr/include/bpf/libbpf.h
root/usr/include/bpf/bpf_core_read.h
root/usr/lib64
root/usr/lib64/libbpf.so.0.1.0
root/usr/lib64/libbpf.so.0
root/usr/lib64/libbpf.a
root/usr/lib64/libbpf.so
root/usr/lib64/pkgconfig
root/usr/lib64/pkgconfig/libbpf.pc

இந்த பிரிவில் எங்கள் அடுத்த திட்டம் பின்வருமாறு: நாங்கள் ஒரு BPF திட்டத்தை எழுதுவோம் BPF_PROG_TYPE_XDP, முந்தைய எடுத்துக்காட்டில் உள்ளதைப் போலவே, ஆனால் C இல், அதைப் பயன்படுத்தி தொகுக்கிறோம் clang, மற்றும் ஒரு உதவி நிரலை எழுதவும், அது கர்னலில் ஏற்றப்படும். பின்வரும் பிரிவுகளில் BPF திட்டம் மற்றும் உதவித் திட்டம் ஆகிய இரண்டின் திறன்களை விரிவுபடுத்துவோம்.

எடுத்துக்காட்டு: libbpf ஐப் பயன்படுத்தி ஒரு முழு அளவிலான பயன்பாட்டை உருவாக்குதல்

தொடங்குவதற்கு, நாங்கள் கோப்பைப் பயன்படுத்துகிறோம் /sys/kernel/btf/vmlinux, இது மேலே குறிப்பிடப்பட்டுள்ளது மற்றும் அதற்கு சமமான தலைப்பு கோப்பின் வடிவத்தில் உருவாக்கவும்:

$ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h

இந்த கோப்பு எங்கள் கர்னலில் கிடைக்கும் அனைத்து தரவு கட்டமைப்புகளையும் சேமிக்கும், எடுத்துக்காட்டாக, கர்னலில் IPv4 தலைப்பு இவ்வாறு வரையறுக்கப்படுகிறது:

$ grep -A 12 'struct iphdr {' vmlinux.h
struct iphdr {
    __u8 ihl: 4;
    __u8 version: 4;
    __u8 tos;
    __be16 tot_len;
    __be16 id;
    __be16 frag_off;
    __u8 ttl;
    __u8 protocol;
    __sum16 check;
    __be32 saddr;
    __be32 daddr;
};

இப்போது நாம் நமது BPF திட்டத்தை C இல் எழுதுவோம்:

$ cat xdp-simple.bpf.c
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

SEC("xdp/simple")
int simple(void *ctx)
{
        return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

எங்கள் திட்டம் மிகவும் எளிமையானதாக மாறியிருந்தாலும், இன்னும் பல விவரங்களுக்கு கவனம் செலுத்த வேண்டும். முதலில், நாம் சேர்க்கும் முதல் தலைப்பு கோப்பு vmlinux.h, நாங்கள் பயன்படுத்தி உருவாக்கியது bpftool btf dump - இப்போது நாம் கர்னல் கட்டமைப்புகள் எப்படி இருக்கும் என்பதைக் கண்டறிய கர்னல்-ஹெடர்ஸ் தொகுப்பை நிறுவ வேண்டியதில்லை. பின்வரும் தலைப்புக் கோப்பு நூலகத்திலிருந்து நமக்கு வருகிறது libbpf. இப்போது நமக்கு மேக்ரோவை வரையறுக்க மட்டுமே தேவை SEC, இது ELF ஆப்ஜெக்ட் கோப்பின் பொருத்தமான பகுதிக்கு எழுத்தை அனுப்புகிறது. எங்கள் நிரல் பிரிவில் உள்ளது xdp/simple, ஸ்லாஷிற்கு முன் நாம் நிரல் வகை BPF ஐ வரையறுக்கிறோம் - இது பயன்படுத்தப்படும் மாநாடு libbpf, பிரிவின் பெயரின் அடிப்படையில் இது தொடக்கத்தில் சரியான வகையை மாற்றும் bpf(2). BPF திட்டமே C - மிகவும் எளிமையானது மற்றும் ஒரு வரியைக் கொண்டுள்ளது return XDP_PASS. இறுதியாக, ஒரு தனி பிரிவு "license" உரிமத்தின் பெயரைக் கொண்டுள்ளது.

llvm/clang, பதிப்பு >= 10.0.0 அல்லது இன்னும் சிறப்பாக, பெரியதைப் பயன்படுத்தி எங்கள் நிரலைத் தொகுக்கலாம் (பிரிவைப் பார்க்கவும் மேம்பாட்டு கருவிகள்):

$ clang --version
clang version 11.0.0 (https://github.com/llvm/llvm-project.git afc287e0abec710398465ee1f86237513f2b5091)
...

$ clang -O2 -g -c -target bpf -I libbpf/src/root/usr/include xdp-simple.bpf.c -o xdp-simple.bpf.o

சுவாரஸ்யமான அம்சங்களில்: இலக்கு கட்டமைப்பைக் குறிப்பிடுகிறோம் -target bpf மற்றும் தலைப்புகளுக்கான பாதை libbpf, நாங்கள் சமீபத்தில் நிறுவியுள்ளோம். மேலும், பற்றி மறக்க வேண்டாம் -O2, இந்த விருப்பம் இல்லாமல் நீங்கள் எதிர்காலத்தில் ஆச்சரியங்களை சந்திக்க நேரிடும். எங்கள் குறியீட்டைப் பார்ப்போம், நாங்கள் விரும்பிய நிரலை எழுத முடிந்ததா?

$ llvm-objdump --section=xdp/simple --no-show-raw-insn -D xdp-simple.bpf.o

xdp-simple.bpf.o:       file format elf64-bpf

Disassembly of section xdp/simple:

0000000000000000 <simple>:
       0:       r0 = 2
       1:       exit

ஆம், அது வேலை செய்தது! இப்போது, ​​எங்களிடம் நிரலுடன் பைனரி கோப்பு உள்ளது, மேலும் அதை கர்னலில் ஏற்றும் ஒரு பயன்பாட்டை உருவாக்க விரும்புகிறோம். இதற்காக நூலகம் libbpf எங்களுக்கு இரண்டு விருப்பங்களை வழங்குகிறது - குறைந்த-நிலை API அல்லது உயர்-நிலை API ஐப் பயன்படுத்தவும். பிபிஎஃப் நிரல்களை எழுதுவது, ஏற்றுவது மற்றும் இணைப்பது எப்படி என்பதை அவற்றின் அடுத்தடுத்த ஆய்வுகளுக்கு குறைந்த முயற்சியுடன் கற்றுக் கொள்ள விரும்புவதால், நாங்கள் இரண்டாவது வழியில் செல்வோம்.

முதலில், எங்கள் நிரலின் "எலும்புக்கூட்டை" அதன் பைனரியில் இருந்து அதே பயன்பாட்டைப் பயன்படுத்தி உருவாக்க வேண்டும் bpftool - பிபிஎஃப் உலகின் சுவிஸ் கத்தி (பிபிஎஃப் உருவாக்கியவர்கள் மற்றும் பராமரிப்பாளர்களில் ஒருவரான டேனியல் போர்க்மேன் சுவிஸ் என்பதால், இதை உண்மையில் எடுத்துக் கொள்ளலாம்):

$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h

கோப்பில் xdp-simple.skel.h எங்கள் நிரலின் பைனரி குறியீடு மற்றும் நிர்வகிப்பதற்கான செயல்பாடுகளை கொண்டுள்ளது - ஏற்றுதல், இணைத்தல், எங்கள் பொருளை நீக்குதல். எங்கள் எளிய விஷயத்தில் இது ஓவர்கில் போல் தெரிகிறது, ஆனால் ஆப்ஜெக்ட் கோப்பில் பல பிபிஎஃப் புரோகிராம்கள் மற்றும் வரைபடங்கள் இருந்தால், இந்த மாபெரும் ELFஐ ஏற்றுவதற்கு நாம் எலும்புக்கூட்டை உருவாக்கி, தனிப்பயன் பயன்பாட்டிலிருந்து ஒன்று அல்லது இரண்டு செயல்பாடுகளை அழைக்க வேண்டும். இப்போது தொடரலாம் என்று எழுதுகிறார்கள்.

கண்டிப்பாகச் சொன்னால், எங்கள் ஏற்றி நிரல் அற்பமானது:

#include <err.h>
#include <unistd.h>
#include "xdp-simple.skel.h"

int main(int argc, char **argv)
{
    struct xdp_simple_bpf *obj;

    obj = xdp_simple_bpf__open_and_load();
    if (!obj)
        err(1, "failed to open and/or load BPF objectn");

    pause();

    xdp_simple_bpf__destroy(obj);
}

இது struct xdp_simple_bpf கோப்பில் வரையறுக்கப்பட்டுள்ளது xdp-simple.skel.h மற்றும் எங்கள் பொருள் கோப்பை விவரிக்கிறது:

struct xdp_simple_bpf {
    struct bpf_object_skeleton *skeleton;
    struct bpf_object *obj;
    struct {
        struct bpf_program *simple;
    } progs;
    struct {
        struct bpf_link *simple;
    } links;
};

குறைந்த-நிலை API இன் தடயங்களை இங்கே காணலாம்: கட்டமைப்பு struct bpf_program *simple и struct bpf_link *simple. முதல் கட்டமைப்பு குறிப்பாக எங்கள் நிரலை விவரிக்கிறது, பிரிவில் எழுதப்பட்டுள்ளது xdp/simple, மற்றும் இரண்டாவது நிகழ்வு மூலத்துடன் நிரல் எவ்வாறு இணைகிறது என்பதை விவரிக்கிறது.

செயல்பாடு xdp_simple_bpf__open_and_load, ஒரு ELF பொருளைத் திறந்து, அதை அலசுகிறது, அனைத்து கட்டமைப்புகள் மற்றும் உட்கட்டமைப்புகளை உருவாக்குகிறது (நிரலைத் தவிர, ELF மற்ற பிரிவுகளையும் கொண்டுள்ளது - தரவு, படிக்க மட்டும் தரவு, பிழைத்திருத்தத் தகவல், உரிமம் போன்றவை), பின்னர் அதை ஒரு கணினியைப் பயன்படுத்தி கர்னலில் ஏற்றுகிறது. அழைப்பு bpf, நிரலை தொகுத்து இயக்குவதன் மூலம் நாம் சரிபார்க்கலாம்:

$ clang -O2 -I ./libbpf/src/root/usr/include/ xdp-simple.c -o xdp-simple ./libbpf/src/root/usr/lib64/libbpf.a -lelf -lz

$ sudo strace -e bpf ./xdp-simple
...
bpf(BPF_BTF_LOAD, 0x7ffdb8fd9670, 120)  = 3
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, insn_cnt=2, insns=0xdfd580, license="GPL", log_level=0, log_size=0, log_buf=NULL, kern_version=KERNEL_VERSION(5, 8, 0), prog_flags=0, prog_name="simple", prog_ifindex=0, expected_attach_type=0x25 /* BPF_??? */, ...}, 120) = 4

இப்போது நமது நிரலைப் பயன்படுத்துவதைப் பார்ப்போம் bpftool. அவளுடைய ஐடியைக் கண்டுபிடிப்போம்:

# bpftool p | grep -A4 simple
463: xdp  name simple  tag 3b185187f1855c4c  gpl
        loaded_at 2020-08-01T01:59:49+0000  uid 0
        xlated 16B  jited 40B  memlock 4096B
        btf_id 185
        pids xdp-simple(16498)

மற்றும் டம்ப் (கட்டளையின் சுருக்கப்பட்ட வடிவத்தைப் பயன்படுத்துகிறோம் bpftool prog dump xlated):

# bpftool p d x id 463
int simple(void *ctx):
; return XDP_PASS;
   0: (b7) r0 = 2
   1: (95) exit

ஏதோ புதியது! நிரல் எங்கள் சி மூல கோப்பின் துண்டுகளை அச்சிட்டது. இது நூலகத்தால் செய்யப்பட்டது libbpf, பைனரியில் பிழைத்திருத்தப் பிரிவைக் கண்டறிந்து, அதை ஒரு BTF பொருளாகத் தொகுத்து, அதைப் பயன்படுத்தி கர்னலில் ஏற்றியது BPF_BTF_LOAD, பின்னர் கட்டளையுடன் நிரலை ஏற்றும் போது விளைவாக கோப்பு விளக்கத்தை குறிப்பிடுகிறது BPG_PROG_LOAD.

கர்னல் உதவியாளர்கள்

BPF நிரல்கள் "வெளிப்புற" செயல்பாடுகளை இயக்கலாம் - கர்னல் உதவியாளர்கள். இந்த உதவி செயல்பாடுகள் BPF நிரல்களை கர்னல் கட்டமைப்புகளை அணுகவும், வரைபடங்களை நிர்வகிக்கவும், மேலும் "உண்மையான உலகத்துடன்" தொடர்பு கொள்ளவும் அனுமதிக்கின்றன - perf நிகழ்வுகளை உருவாக்குதல், வன்பொருளைக் கட்டுப்படுத்துதல் (எடுத்துக்காட்டாக, பாக்கெட்டுகள்) போன்றவை.

எடுத்துக்காட்டு: bpf_get_smp_processor_id

"எடுத்துக்காட்டு மூலம் கற்றல்" முன்னுதாரணத்தின் கட்டமைப்பிற்குள், உதவி செயல்பாடுகளில் ஒன்றைக் கருத்தில் கொள்வோம், bpf_get_smp_processor_id(), உறுதி கோப்பில் kernel/bpf/helpers.c. BPF நிரல் இயங்கும் செயலியின் எண்ணை இது வழங்குகிறது. ஆனால் அதன் செயல்படுத்தல் ஒரு வரியை எடுக்கும் என்ற உண்மையைப் போல அதன் சொற்பொருளில் நாங்கள் ஆர்வம் காட்டவில்லை:

BPF_CALL_0(bpf_get_smp_processor_id)
{
    return smp_processor_id();
}

BPF உதவி செயல்பாட்டின் வரையறைகள் லினக்ஸ் சிஸ்டம் அழைப்பு வரையறைகளைப் போலவே இருக்கும். இங்கே, எடுத்துக்காட்டாக, வாதங்கள் இல்லாத ஒரு செயல்பாடு வரையறுக்கப்படுகிறது. (மூன்று வாதங்களை எடுக்கும் ஒரு செயல்பாடு மேக்ரோவைப் பயன்படுத்தி வரையறுக்கப்படுகிறது BPF_CALL_3. வாதங்களின் அதிகபட்ச எண்ணிக்கை ஐந்து.) இருப்பினும், இது வரையறையின் முதல் பகுதி மட்டுமே. இரண்டாவது பகுதி வகை கட்டமைப்பை வரையறுக்க வேண்டும் struct bpf_func_proto, சரிபார்ப்பவர் புரிந்துகொள்ளும் உதவி செயல்பாட்டின் விளக்கத்தைக் கொண்டுள்ளது:

const struct bpf_func_proto bpf_get_smp_processor_id_proto = {
    .func     = bpf_get_smp_processor_id,
    .gpl_only = false,
    .ret_type = RET_INTEGER,
};

உதவி செயல்பாடுகளை பதிவு செய்தல்

ஒரு குறிப்பிட்ட வகை BPF நிரல்கள் இந்தச் செயல்பாட்டைப் பயன்படுத்த, அவர்கள் அதை பதிவு செய்ய வேண்டும், எடுத்துக்காட்டாக வகைக்கு BPF_PROG_TYPE_XDP ஒரு செயல்பாடு கர்னலில் வரையறுக்கப்படுகிறது xdp_func_proto, XDP இந்தச் செயல்பாட்டை ஆதரிக்கிறதா இல்லையா என்பதை ஹெல்பர் ஃபங்ஷன் ஐடியில் இருந்து தீர்மானிக்கிறது. நமது செயல்பாடு ஆதரிக்கிறது:

static const struct bpf_func_proto *
xdp_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
{
    switch (func_id) {
    ...
    case BPF_FUNC_get_smp_processor_id:
        return &bpf_get_smp_processor_id_proto;
    ...
    }
}

புதிய BPF நிரல் வகைகள் கோப்பில் "வரையறுக்கப்பட்டுள்ளன" include/linux/bpf_types.h ஒரு மேக்ரோ பயன்படுத்தி BPF_PROG_TYPE. இது ஒரு தர்க்கரீதியான வரையறை என்பதால் மேற்கோள்களில் வரையறுக்கப்படுகிறது, மேலும் சி மொழியின் அடிப்படையில் முழு கான்கிரீட் கட்டமைப்புகளின் வரையறை மற்ற இடங்களில் நிகழ்கிறது. குறிப்பாக, கோப்பில் kernel/bpf/verifier.c கோப்பிலிருந்து அனைத்து வரையறைகளும் bpf_types.h கட்டமைப்புகளின் வரிசையை உருவாக்க பயன்படுகிறது bpf_verifier_ops[]:

static const struct bpf_verifier_ops *const bpf_verifier_ops[] = {
#define BPF_PROG_TYPE(_id, _name, prog_ctx_type, kern_ctx_type) 
    [_id] = & _name ## _verifier_ops,
#include <linux/bpf_types.h>
#undef BPF_PROG_TYPE
};

அதாவது, ஒவ்வொரு வகை BPF நிரலுக்கும், வகையின் தரவுக் கட்டமைப்பிற்கான ஒரு சுட்டி வரையறுக்கப்படுகிறது struct bpf_verifier_ops, இது மதிப்புடன் துவக்கப்படுகிறது _name ## _verifier_ops, அதாவது, xdp_verifier_ops செய்ய xdp. கட்டமைப்பு xdp_verifier_ops தீர்மானிக்கப்படுகிறது கோப்பில் net/core/filter.c பின்வருமாறு:

const struct bpf_verifier_ops xdp_verifier_ops = {
    .get_func_proto     = xdp_func_proto,
    .is_valid_access    = xdp_is_valid_access,
    .convert_ctx_access = xdp_convert_ctx_access,
    .gen_prologue       = bpf_noop_prologue,
};

இங்கே நாம் நமக்குத் தெரிந்த செயல்பாட்டைக் காண்கிறோம் xdp_func_proto, இது ஒரு சவாலை எதிர்கொள்ளும் ஒவ்வொரு முறையும் சரிபார்ப்பை இயக்கும் சில வகையான BPF நிரலுக்குள் செயல்பாடுகள், பார்க்கவும் verifier.c.

ஒரு அனுமான BPF நிரல் செயல்பாட்டை எவ்வாறு பயன்படுத்துகிறது என்பதைப் பார்ப்போம் bpf_get_smp_processor_id. இதைச் செய்ய, எங்கள் முந்தைய பிரிவில் இருந்து நிரலை பின்வருமாறு மீண்டும் எழுதுகிறோம்:

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

SEC("xdp/simple")
int simple(void *ctx)
{
    if (bpf_get_smp_processor_id() != 0)
        return XDP_DROP;
    return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

சின்னமாக bpf_get_smp_processor_id தீர்மானிக்கப்படுகிறது в <bpf/bpf_helper_defs.h> நூலகம் libbpf எப்படி

static u32 (*bpf_get_smp_processor_id)(void) = (void *) 8;

அது, bpf_get_smp_processor_id ஒரு செயல்பாடு சுட்டிக்காட்டி அதன் மதிப்பு 8 ஆகும், இதில் 8 என்பது மதிப்பு BPF_FUNC_get_smp_processor_id வகை enum bpf_fun_id, இது கோப்பில் நமக்கு வரையறுக்கப்பட்டுள்ளது vmlinux.h (கோப்பு bpf_helper_defs.h கர்னலில் ஒரு ஸ்கிரிப்ட் உருவாக்கப்படுகிறது, எனவே "மேஜிக்" எண்கள் சரி). இந்த செயல்பாடு எந்த வாதங்களையும் எடுக்காது மற்றும் வகையின் மதிப்பை வழங்குகிறது __u32. எங்கள் நிரலில் அதை இயக்கும்போது, clang ஒரு அறிவுறுத்தலை உருவாக்குகிறது BPF_CALL "சரியான வகை" நிரலைத் தொகுத்து, பகுதியைப் பார்ப்போம் xdp/simple:

$ clang -O2 -g -c -target bpf -I libbpf/src/root/usr/include xdp-simple.bpf.c -o xdp-simple.bpf.o
$ llvm-objdump -D --section=xdp/simple xdp-simple.bpf.o

xdp-simple.bpf.o:       file format elf64-bpf

Disassembly of section xdp/simple:

0000000000000000 <simple>:
       0:       85 00 00 00 08 00 00 00 call 8
       1:       bf 01 00 00 00 00 00 00 r1 = r0
       2:       67 01 00 00 20 00 00 00 r1 <<= 32
       3:       77 01 00 00 20 00 00 00 r1 >>= 32
       4:       b7 00 00 00 02 00 00 00 r0 = 2
       5:       15 01 01 00 00 00 00 00 if r1 == 0 goto +1 <LBB0_2>
       6:       b7 00 00 00 01 00 00 00 r0 = 1

0000000000000038 <LBB0_2>:
       7:       95 00 00 00 00 00 00 00 exit

முதல் வரியில் நாம் வழிமுறைகளைப் பார்க்கிறோம் call, அளவுரு IMM இது 8 க்கு சமம், மற்றும் SRC_REG - பூஜ்யம். சரிபார்ப்பாளரால் பயன்படுத்தப்படும் ஏபிஐ ஒப்பந்தத்தின்படி, இது ஹெல்பர் செயல்பாடு எண் எட்டிற்கான அழைப்பு. அது தொடங்கப்பட்டவுடன், தர்க்கம் எளிது. பதிவேட்டில் இருந்து மதிப்பு திரும்ப r0 நகலெடுக்கப்பட்டது r1 மற்றும் வரிகள் 2,3 இல் இது வகைக்கு மாற்றப்படுகிறது u32 - மேல் 32 பிட்கள் அழிக்கப்பட்டன. 4,5,6,7 வரிகளில் நாம் 2 ஐத் தருகிறோம் (XDP_PASS) அல்லது 1 (XDP_DROP) வரி 0 இலிருந்து உதவியாளர் செயல்பாடு பூஜ்ஜியமா அல்லது பூஜ்ஜியமற்ற மதிப்பை வழங்கியதா என்பதைப் பொறுத்து.

நம்மை நாமே சோதித்துக் கொள்வோம்: நிரலை ஏற்றி வெளியீட்டைப் பாருங்கள் bpftool prog dump xlated:

$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h
$ clang -O2 -g -I ./libbpf/src/root/usr/include/ -o xdp-simple xdp-simple.c ./libbpf/src/root/usr/lib64/libbpf.a -lelf -lz
$ sudo ./xdp-simple &
[2] 10914

$ sudo bpftool p | grep simple
523: xdp  name simple  tag 44c38a10c657e1b0  gpl
        pids xdp-simple(10915)

$ sudo bpftool p d x id 523
int simple(void *ctx):
; if (bpf_get_smp_processor_id() != 0)
   0: (85) call bpf_get_smp_processor_id#114128
   1: (bf) r1 = r0
   2: (67) r1 <<= 32
   3: (77) r1 >>= 32
   4: (b7) r0 = 2
; }
   5: (15) if r1 == 0x0 goto pc+1
   6: (b7) r0 = 1
   7: (95) exit

சரி, சரிபார்ப்பாளர் சரியான கர்னல்-உதவியைக் கண்டுபிடித்தார்.

எடுத்துக்காட்டு: வாதங்களை கடந்து இறுதியாக நிரலை இயக்குதல்!

அனைத்து ரன்-லெவல் ஹெல்பர் செயல்பாடுகளுக்கும் ஒரு முன்மாதிரி உள்ளது

u64 fn(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)

உதவி செயல்பாடுகளுக்கான அளவுருக்கள் பதிவேடுகளில் அனுப்பப்படுகின்றன r1-r5, மற்றும் மதிப்பு பதிவேட்டில் திரும்பும் r0. ஐந்து வாதங்களுக்கு மேல் எடுக்கும் செயல்பாடுகள் எதுவும் இல்லை, மேலும் அவற்றுக்கான ஆதரவு எதிர்காலத்தில் சேர்க்கப்படும் என்று எதிர்பார்க்கப்படுவதில்லை.

புதிய கர்னல் உதவியாளர் மற்றும் BPF அளவுருக்களை எவ்வாறு கடந்து செல்கிறது என்பதைப் பார்ப்போம். மீண்டும் எழுதுவோம் xdp-simple.bpf.c பின்வருமாறு (மீதமுள்ள வரிகள் மாறவில்லை):

SEC("xdp/simple")
int simple(void *ctx)
{
    bpf_printk("running on CPU%un", bpf_get_smp_processor_id());
    return XDP_PASS;
}

எங்கள் நிரல் அது இயங்கும் CPU இன் எண்ணை அச்சிடுகிறது. அதை தொகுத்து குறியீட்டைப் பார்ப்போம்:

$ llvm-objdump -D --section=xdp/simple --no-show-raw-insn xdp-simple.bpf.o

0000000000000000 <simple>:
       0:       r1 = 10
       1:       *(u16 *)(r10 - 8) = r1
       2:       r1 = 8441246879787806319 ll
       4:       *(u64 *)(r10 - 16) = r1
       5:       r1 = 2334956330918245746 ll
       7:       *(u64 *)(r10 - 24) = r1
       8:       call 8
       9:       r1 = r10
      10:       r1 += -24
      11:       r2 = 18
      12:       r3 = r0
      13:       call 6
      14:       r0 = 2
      15:       exit

0-7 வரிகளில் நாம் சரத்தை எழுதுகிறோம் running on CPU%un, பின்னர் வரி 8 இல் நாம் பழக்கமான ஒன்றை இயக்குகிறோம் bpf_get_smp_processor_id. 9-12 வரிகளில் நாங்கள் உதவி வாதங்களை தயார் செய்கிறோம் bpf_printk - பதிவு செய்கிறது r1, r2, r3. அவற்றில் மூன்று ஏன் இரண்டு இல்லை? ஏனெனில் bpf_printkஇது ஒரு மேக்ரோ ரேப்பர் உண்மையான உதவியாளரைச் சுற்றி bpf_trace_printk, இது வடிவமைப்பு சரத்தின் அளவைக் கடக்க வேண்டும்.

இப்போது ஒன்றிரண்டு வரிகளைச் சேர்ப்போம் xdp-simple.cஅதனால் எங்கள் நிரல் இடைமுகத்துடன் இணைகிறது lo மற்றும் உண்மையில் தொடங்கியது!

$ cat xdp-simple.c
#include <linux/if_link.h>
#include <err.h>
#include <unistd.h>
#include "xdp-simple.skel.h"

int main(int argc, char **argv)
{
    __u32 flags = XDP_FLAGS_SKB_MODE;
    struct xdp_simple_bpf *obj;

    obj = xdp_simple_bpf__open_and_load();
    if (!obj)
        err(1, "failed to open and/or load BPF objectn");

    bpf_set_link_xdp_fd(1, -1, flags);
    bpf_set_link_xdp_fd(1, bpf_program__fd(obj->progs.simple), flags);

cleanup:
    xdp_simple_bpf__destroy(obj);
}

இங்கே நாம் செயல்பாட்டைப் பயன்படுத்துகிறோம் bpf_set_link_xdp_fd, இது XDP-வகை BPF நிரல்களை பிணைய இடைமுகங்களுடன் இணைக்கிறது. இடைமுக எண்ணை ஹார்ட்கோட் செய்தோம் lo, இது எப்போதும் 1. பழைய நிரல் இணைக்கப்பட்டிருந்தால் அதை முதலில் துண்டிக்க இரண்டு முறை செயல்பாட்டை இயக்குகிறோம். இப்போது எங்களுக்கு ஒரு சவால் தேவையில்லை என்பதைக் கவனியுங்கள் pause அல்லது எல்லையற்ற வளையம்: எங்கள் ஏற்றி நிரல் வெளியேறும், ஆனால் நிகழ்வு மூலத்துடன் இணைக்கப்பட்டுள்ளதால் BPF நிரல் அழிக்கப்படாது. வெற்றிகரமான பதிவிறக்கம் மற்றும் இணைப்புக்குப் பிறகு, ஒவ்வொரு நெட்வொர்க் பாக்கெட்டுக்கும் நிரல் தொடங்கப்படும் lo.

நிரலை பதிவிறக்கம் செய்து இடைமுகத்தைப் பார்ப்போம் lo:

$ sudo ./xdp-simple
$ sudo bpftool p | grep simple
669: xdp  name simple  tag 4fca62e77ccb43d6  gpl
$ ip l show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    prog/xdp id 669

நாங்கள் பதிவிறக்கிய நிரலில் ஐடி 669 உள்ளது மற்றும் இடைமுகத்தில் அதே ஐடியைப் பார்க்கிறோம் lo. இரண்டு தொகுப்புகளை அனுப்புவோம் 127.0.0.1 (கோரிக்கை + பதில்):

$ ping -c1 localhost

இப்போது பிழைத்திருத்த மெய்நிகர் கோப்பின் உள்ளடக்கங்களைப் பார்ப்போம் /sys/kernel/debug/tracing/trace_pipe, இதில் bpf_printk அவரது செய்திகளை எழுதுகிறார்:

# cat /sys/kernel/debug/tracing/trace_pipe
ping-13937 [000] d.s1 442015.377014: bpf_trace_printk: running on CPU0
ping-13937 [000] d.s1 442015.377027: bpf_trace_printk: running on CPU0

இரண்டு தொகுப்புகள் காணப்பட்டன lo மற்றும் CPU0 இல் செயலாக்கப்பட்டது - எங்கள் முதல் முழு அளவிலான அர்த்தமற்ற BPF நிரல் வேலை செய்தது!

என்பது குறிப்பிடத்தக்கது bpf_printk இது பிழைத்திருத்தக் கோப்பில் எழுதுவது ஒன்றும் இல்லை: இது தயாரிப்பில் பயன்படுத்த மிகவும் வெற்றிகரமான உதவியாளர் அல்ல, ஆனால் எளிமையான ஒன்றைக் காண்பிப்பதே எங்கள் குறிக்கோளாக இருந்தது.

BPF திட்டங்களிலிருந்து வரைபடங்களை அணுகுதல்

எடுத்துக்காட்டு: BPF திட்டத்திலிருந்து ஒரு வரைபடத்தைப் பயன்படுத்துதல்

முந்தைய பிரிவுகளில் பயனர் இடத்திலிருந்து வரைபடங்களை எவ்வாறு உருவாக்குவது மற்றும் பயன்படுத்துவது என்பதைக் கற்றுக்கொண்டோம், இப்போது கர்னல் பகுதியைப் பார்ப்போம். வழக்கம் போல், ஒரு உதாரணத்துடன் தொடங்குவோம். எங்கள் திட்டத்தை மீண்டும் எழுதுவோம் xdp-simple.bpf.c பின்வருமாறு:

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(max_entries, 8);
    __type(key, u32);
    __type(value, u64);
} woo SEC(".maps");

SEC("xdp/simple")
int simple(void *ctx)
{
    u32 key = bpf_get_smp_processor_id();
    u32 *val;

    val = bpf_map_lookup_elem(&woo, &key);
    if (!val)
        return XDP_ABORTED;

    *val += 1;

    return XDP_PASS;
}

char LICENSE[] SEC("license") = "GPL";

நிரலின் தொடக்கத்தில் வரைபட வரையறையைச் சேர்த்துள்ளோம் woo: இது போன்ற மதிப்புகளைச் சேமிக்கும் 8-உறுப்பு வரிசை u64 (C இல் நாம் அத்தகைய வரிசையை வரையறுப்போம் u64 woo[8]) ஒரு திட்டத்தில் "xdp/simple" தற்போதைய செயலி எண்ணை மாறியாகப் பெறுகிறோம் key பின்னர் உதவி செயல்பாட்டைப் பயன்படுத்தவும் bpf_map_lookup_element வரிசையில் உள்ள தொடர்புடைய உள்ளீட்டிற்கு ஒரு சுட்டியைப் பெறுகிறோம், அதை ஒன்றால் அதிகரிக்கிறோம். ரஷ்ய மொழியில் மொழிபெயர்க்கப்பட்டுள்ளது: உள்வரும் பாக்கெட்டுகளை CPU செயலாக்கிய புள்ளிவிவரங்களைக் கணக்கிடுகிறோம். நிரலை இயக்க முயற்சிப்போம்:

$ clang -O2 -g -c -target bpf -I libbpf/src/root/usr/include xdp-simple.bpf.c -o xdp-simple.bpf.o
$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h
$ clang -O2 -g -I ./libbpf/src/root/usr/include/ -o xdp-simple xdp-simple.c ./libbpf/src/root/usr/lib64/libbpf.a -lelf -lz
$ sudo ./xdp-simple

அவள் இணந்துவிட்டாளா என்று பார்ப்போம் lo மற்றும் சில பாக்கெட்டுகளை அனுப்பவும்:

$ ip l show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    prog/xdp id 108

$ for s in `seq 234`; do sudo ping -f -c 100 127.0.0.1 >/dev/null 2>&1; done

இப்போது வரிசையின் உள்ளடக்கங்களைப் பார்ப்போம்:

$ sudo bpftool map dump name woo
[
    { "key": 0, "value": 0 },
    { "key": 1, "value": 400 },
    { "key": 2, "value": 0 },
    { "key": 3, "value": 0 },
    { "key": 4, "value": 0 },
    { "key": 5, "value": 0 },
    { "key": 6, "value": 0 },
    { "key": 7, "value": 46400 }
]

கிட்டத்தட்ட அனைத்து செயல்முறைகளும் CPU7 இல் செயலாக்கப்பட்டன. இது எங்களுக்கு முக்கியமல்ல, முக்கிய விஷயம் என்னவென்றால், நிரல் வேலை செய்கிறது மற்றும் BPF நிரல்களிலிருந்து வரைபடங்களை எவ்வாறு அணுகுவது என்பதை நாங்கள் புரிந்துகொள்கிறோம். хелперов bpf_mp_*.

மாய குறியீடு

எனவே, போன்ற அழைப்புகளைப் பயன்படுத்தி BPF திட்டத்திலிருந்து வரைபடத்தை அணுகலாம்

val = bpf_map_lookup_elem(&woo, &key);

உதவியாளர் செயல்பாடு எங்கே தெரிகிறது

void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)

ஆனால் நாம் ஒரு சுட்டியை கடந்து செல்கிறோம் &woo பெயரிடப்படாத கட்டமைப்பிற்கு struct { ... }...

நிரல் அசெம்பிளரைப் பார்த்தால், அதன் மதிப்பைக் காண்கிறோம் &woo உண்மையில் வரையறுக்கப்படவில்லை (வரி 4):

llvm-objdump -D --section xdp/simple xdp-simple.bpf.o

xdp-simple.bpf.o:       file format elf64-bpf

Disassembly of section xdp/simple:

0000000000000000 <simple>:
       0:       85 00 00 00 08 00 00 00 call 8
       1:       63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0
       2:       bf a2 00 00 00 00 00 00 r2 = r10
       3:       07 02 00 00 fc ff ff ff r2 += -4
       4:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll
       6:       85 00 00 00 01 00 00 00 call 1
...

மற்றும் இடமாற்றங்களில் அடங்கியுள்ளது:

$ llvm-readelf -r xdp-simple.bpf.o | head -4

Relocation section '.relxdp/simple' at offset 0xe18 contains 1 entries:
    Offset             Info             Type               Symbol's Value  Symbol's Name
0000000000000020  0000002700000001 R_BPF_64_64            0000000000000000 woo

ஆனால் ஏற்கனவே ஏற்றப்பட்ட நிரலைப் பார்த்தால், சரியான வரைபடத்திற்கான ஒரு சுட்டியைக் காண்கிறோம் (வரி 4):

$ sudo bpftool prog dump x name simple
int simple(void *ctx):
   0: (85) call bpf_get_smp_processor_id#114128
   1: (63) *(u32 *)(r10 -4) = r0
   2: (bf) r2 = r10
   3: (07) r2 += -4
   4: (18) r1 = map[id:64]
...

எனவே, எங்கள் ஏற்றி நிரலைத் தொடங்கும் நேரத்தில், அதற்கான இணைப்பு என்று நாம் முடிவு செய்யலாம் &woo ஏதோ ஒரு நூலகத்தால் மாற்றப்பட்டது libbpf. முதலில் நாம் வெளியீட்டைப் பார்ப்போம் strace:

$ sudo strace -e bpf ./xdp-simple
...
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_ARRAY, key_size=4, value_size=8, max_entries=8, map_name="woo", ...}, 120) = 4
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, prog_name="simple", ...}, 120) = 5

என்று பார்க்கிறோம் libbpf ஒரு வரைபடத்தை உருவாக்கியது woo பின்னர் எங்கள் திட்டத்தை பதிவிறக்கம் செய்தேன் simple. நிரலை எவ்வாறு ஏற்றுவது என்பதை விரிவாகப் பார்ப்போம்:

  • அழைப்பு xdp_simple_bpf__open_and_load கோப்பில் இருந்து xdp-simple.skel.h
  • ஏற்படுத்துகிறது xdp_simple_bpf__load கோப்பில் இருந்து xdp-simple.skel.h
  • ஏற்படுத்துகிறது bpf_object__load_skeleton கோப்பில் இருந்து libbpf/src/libbpf.c
  • ஏற்படுத்துகிறது bpf_object__load_xattr из libbpf/src/libbpf.c

கடைசி செயல்பாடு, மற்றவற்றுடன், அழைக்கும் bpf_object__create_maps, இது ஏற்கனவே உள்ள வரைபடங்களை உருவாக்குகிறது அல்லது திறக்கிறது, அவற்றை கோப்பு விளக்கங்களாக மாற்றுகிறது. (இங்கே நாம் பார்க்கிறோம் BPF_MAP_CREATE வெளியீட்டில் strace.) அடுத்து செயல்பாடு அழைக்கப்படுகிறது bpf_object__relocate நாம் பார்த்ததை நினைவில் வைத்திருப்பதால், அவள்தான் எங்களுக்கு ஆர்வமாக இருக்கிறாள் woo இடமாற்ற அட்டவணையில். அதை ஆராய்ந்து, இறுதியில் செயல்பாட்டில் நம்மைக் காண்கிறோம் bpf_program__relocate, எந்த வரைபட இடமாற்றங்களை கையாள்கிறது:

case RELO_LD64:
    insn[0].src_reg = BPF_PSEUDO_MAP_FD;
    insn[0].imm = obj->maps[relo->map_idx].fd;
    break;

எனவே நாங்கள் எங்கள் வழிமுறைகளை எடுத்துக்கொள்கிறோம்

18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll

மற்றும் அதில் உள்ள மூலப் பதிவேட்டை மாற்றவும் BPF_PSEUDO_MAP_FD, மற்றும் எங்கள் வரைபடத்தின் கோப்பு விளக்கத்திற்கான முதல் IMM மற்றும் அது சமமாக இருந்தால், எடுத்துக்காட்டாக, 0xdeadbeef, அதன் விளைவாக நாம் அறிவுறுத்தலைப் பெறுவோம்

18 11 00 00 ef eb ad de 00 00 00 00 00 00 00 00 r1 = 0 ll

ஒரு குறிப்பிட்ட ஏற்றப்பட்ட BPF திட்டத்திற்கு வரைபடத் தகவல் இவ்வாறு மாற்றப்படுகிறது. இந்த வழக்கில், வரைபடத்தை பயன்படுத்தி உருவாக்க முடியும் BPF_MAP_CREATE, மற்றும் ஐடி மூலம் திறக்கப்பட்டது BPF_MAP_GET_FD_BY_ID.

மொத்தம், பயன்படுத்தும் போது libbpf வழிமுறை பின்வருமாறு:

  • தொகுப்பின் போது, ​​வரைபடங்களுக்கான இணைப்புகளுக்கான இடமாற்ற அட்டவணையில் பதிவுகள் உருவாக்கப்படுகின்றன
  • libbpf ELF ஆப்ஜெக்ட் புத்தகத்தைத் திறந்து, பயன்படுத்தப்பட்ட அனைத்து வரைபடங்களையும் கண்டுபிடித்து அவற்றுக்கான கோப்பு விளக்கங்களை உருவாக்குகிறது
  • அறிவுறுத்தலின் ஒரு பகுதியாக கோப்பு விளக்கங்கள் கர்னலில் ஏற்றப்படுகின்றன LD64

நீங்கள் கற்பனை செய்வது போல், இன்னும் நிறைய வர இருக்கிறது, நாம் மையத்தை பார்க்க வேண்டும். அதிர்ஷ்டவசமாக, எங்களிடம் ஒரு துப்பு உள்ளது - நாங்கள் அர்த்தத்தை எழுதியுள்ளோம் BPF_PSEUDO_MAP_FD மூலப் பதிவேட்டில் நாம் அதை அடக்கம் செய்யலாம், இது அனைத்து புனிதர்களின் புனித இடத்திற்கு நம்மை அழைத்துச் செல்லும் - kernel/bpf/verifier.c, ஒரு தனித்துவமான பெயரைக் கொண்ட ஒரு செயல்பாடு ஒரு கோப்பு விளக்கத்தை மாற்றியமைக்கும் வகையின் கட்டமைப்பின் முகவரி struct bpf_map:

static int replace_map_fd_with_map_ptr(struct bpf_verifier_env *env) {
    ...

    f = fdget(insn[0].imm);
    map = __bpf_map_get(f);
    if (insn->src_reg == BPF_PSEUDO_MAP_FD) {
        addr = (unsigned long)map;
    }
    insn[0].imm = (u32)addr;
    insn[1].imm = addr >> 32;

(முழு குறியீடு காணலாம் இணைப்பு) எனவே நமது வழிமுறையை விரிவாக்கலாம்:

  • நிரலை ஏற்றும் போது, ​​சரிபார்ப்பவர் வரைபடத்தின் சரியான பயன்பாட்டை சரிபார்த்து, தொடர்புடைய கட்டமைப்பின் முகவரியை எழுதுகிறார் struct bpf_map

பயன்படுத்தி ELF பைனரி பதிவிறக்கம் போது libbpf இன்னும் நிறைய நடக்கிறது, ஆனால் அதை மற்ற கட்டுரைகளில் விவாதிப்போம்.

libbpf இல்லாமல் நிரல்களையும் வரைபடங்களையும் ஏற்றுகிறது

வாக்குறுதியளித்தபடி, உதவியின்றி வரைபடங்களைப் பயன்படுத்தும் நிரலை எவ்வாறு உருவாக்குவது மற்றும் ஏற்றுவது என்பதை அறிய விரும்பும் வாசகர்களுக்கு இங்கே ஒரு எடுத்துக்காட்டு libbpf. நீங்கள் சார்புகளை உருவாக்க முடியாத சூழலில் பணிபுரியும் போது அல்லது ஒவ்வொரு பிட்டையும் சேமிக்கும் போது அல்லது ஒரு நிரலை எழுதும் போது இது பயனுள்ளதாக இருக்கும். ply, இது பறக்கும்போது BPF பைனரி குறியீட்டை உருவாக்குகிறது.

தர்க்கத்தைப் பின்பற்றுவதை எளிதாக்க, இந்த நோக்கங்களுக்காக எங்கள் உதாரணத்தை மீண்டும் எழுதுவோம் xdp-simple. இந்த எடுத்துக்காட்டில் விவாதிக்கப்பட்ட நிரலின் முழுமையான மற்றும் சற்று விரிவாக்கப்பட்ட குறியீட்டை இதில் காணலாம் சாராம்சம்.

எங்கள் விண்ணப்பத்தின் தர்க்கம் பின்வருமாறு:

  • ஒரு வகை வரைபடத்தை உருவாக்கவும் BPF_MAP_TYPE_ARRAY கட்டளையை பயன்படுத்தி BPF_MAP_CREATE,
  • இந்த வரைபடத்தைப் பயன்படுத்தி ஒரு நிரலை உருவாக்கவும்,
  • நிரலை இடைமுகத்துடன் இணைக்கவும் lo,

இது மனிதனாக மொழிபெயர்க்கப்பட்டுள்ளது

int main(void)
{
    int map_fd, prog_fd;

    map_fd = map_create();
    if (map_fd < 0)
        err(1, "bpf: BPF_MAP_CREATE");

    prog_fd = prog_load(map_fd);
    if (prog_fd < 0)
        err(1, "bpf: BPF_PROG_LOAD");

    xdp_attach(1, prog_fd);
}

இது map_create கணினி அழைப்பைப் பற்றிய முதல் எடுத்துக்காட்டில் நாம் செய்ததைப் போலவே ஒரு வரைபடத்தை உருவாக்குகிறது bpf - “கர்னல், தயவு செய்து எனக்கு ஒரு புதிய வரைபடத்தை 8 கூறுகளின் வரிசையின் வடிவத்தில் உருவாக்கவும் __u64 கோப்பு விளக்கத்தை என்னிடம் திருப்பித் தரவும்":

static int map_create()
{
    union bpf_attr attr;

    memset(&attr, 0, sizeof(attr));
    attr.map_type = BPF_MAP_TYPE_ARRAY,
    attr.key_size = sizeof(__u32),
    attr.value_size = sizeof(__u64),
    attr.max_entries = 8,
    strncpy(attr.map_name, "woo", sizeof(attr.map_name));
    return syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr));
}

நிரலை ஏற்றுவதும் எளிதானது:

static int prog_load(int map_fd)
{
    union bpf_attr attr;
    struct bpf_insn insns[] = {
        ...
    };

    memset(&attr, 0, sizeof(attr));
    attr.prog_type = BPF_PROG_TYPE_XDP;
    attr.insns     = ptr_to_u64(insns);
    attr.insn_cnt  = sizeof(insns)/sizeof(insns[0]);
    attr.license   = ptr_to_u64("GPL");
    strncpy(attr.prog_name, "woo", sizeof(attr.prog_name));
    return syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr));
}

தந்திரமான பகுதி prog_load எங்கள் BPF திட்டத்தின் வரையறை கட்டமைப்புகளின் வரிசையாக உள்ளது struct bpf_insn insns[]. ஆனால் நாம் C இல் உள்ள ஒரு நிரலைப் பயன்படுத்துவதால், நாம் கொஞ்சம் ஏமாற்றலாம்:

$ llvm-objdump -D --section xdp/simple xdp-simple.bpf.o

0000000000000000 <simple>:
       0:       85 00 00 00 08 00 00 00 call 8
       1:       63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0
       2:       bf a2 00 00 00 00 00 00 r2 = r10
       3:       07 02 00 00 fc ff ff ff r2 += -4
       4:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll
       6:       85 00 00 00 01 00 00 00 call 1
       7:       b7 01 00 00 00 00 00 00 r1 = 0
       8:       15 00 04 00 00 00 00 00 if r0 == 0 goto +4 <LBB0_2>
       9:       61 01 00 00 00 00 00 00 r1 = *(u32 *)(r0 + 0)
      10:       07 01 00 00 01 00 00 00 r1 += 1
      11:       63 10 00 00 00 00 00 00 *(u32 *)(r0 + 0) = r1
      12:       b7 01 00 00 02 00 00 00 r1 = 2

0000000000000068 <LBB0_2>:
      13:       bf 10 00 00 00 00 00 00 r0 = r1
      14:       95 00 00 00 00 00 00 00 exit

மொத்தத்தில், போன்ற கட்டமைப்புகளின் வடிவத்தில் 14 வழிமுறைகளை எழுத வேண்டும் struct bpf_insn (ஆலோசனை: மேலே இருந்து குப்பையை எடுத்து, வழிமுறைகள் பகுதியை மீண்டும் படிக்கவும், திறக்கவும் linux/bpf.h и linux/bpf_common.h மற்றும் தீர்மானிக்க முயற்சிக்கவும் struct bpf_insn insns[] சொந்தமாக):

struct bpf_insn insns[] = {
    /* 85 00 00 00 08 00 00 00 call 8 */
    {
        .code = BPF_JMP | BPF_CALL,
        .imm = 8,
    },

    /* 63 0a fc ff 00 00 00 00 *(u32 *)(r10 - 4) = r0 */
    {
        .code = BPF_MEM | BPF_STX,
        .off = -4,
        .src_reg = BPF_REG_0,
        .dst_reg = BPF_REG_10,
    },

    /* bf a2 00 00 00 00 00 00 r2 = r10 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_X,
        .src_reg = BPF_REG_10,
        .dst_reg = BPF_REG_2,
    },

    /* 07 02 00 00 fc ff ff ff r2 += -4 */
    {
        .code = BPF_ALU64 | BPF_ADD | BPF_K,
        .dst_reg = BPF_REG_2,
        .imm = -4,
    },

    /* 18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0 ll */
    {
        .code = BPF_LD | BPF_DW | BPF_IMM,
        .src_reg = BPF_PSEUDO_MAP_FD,
        .dst_reg = BPF_REG_1,
        .imm = map_fd,
    },
    { }, /* placeholder */

    /* 85 00 00 00 01 00 00 00 call 1 */
    {
        .code = BPF_JMP | BPF_CALL,
        .imm = 1,
    },

    /* b7 01 00 00 00 00 00 00 r1 = 0 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_K,
        .dst_reg = BPF_REG_1,
        .imm = 0,
    },

    /* 15 00 04 00 00 00 00 00 if r0 == 0 goto +4 <LBB0_2> */
    {
        .code = BPF_JMP | BPF_JEQ | BPF_K,
        .off = 4,
        .src_reg = BPF_REG_0,
        .imm = 0,
    },

    /* 61 01 00 00 00 00 00 00 r1 = *(u32 *)(r0 + 0) */
    {
        .code = BPF_MEM | BPF_LDX,
        .off = 0,
        .src_reg = BPF_REG_0,
        .dst_reg = BPF_REG_1,
    },

    /* 07 01 00 00 01 00 00 00 r1 += 1 */
    {
        .code = BPF_ALU64 | BPF_ADD | BPF_K,
        .dst_reg = BPF_REG_1,
        .imm = 1,
    },

    /* 63 10 00 00 00 00 00 00 *(u32 *)(r0 + 0) = r1 */
    {
        .code = BPF_MEM | BPF_STX,
        .src_reg = BPF_REG_1,
        .dst_reg = BPF_REG_0,
    },

    /* b7 01 00 00 02 00 00 00 r1 = 2 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_K,
        .dst_reg = BPF_REG_1,
        .imm = 2,
    },

    /* <LBB0_2>: bf 10 00 00 00 00 00 00 r0 = r1 */
    {
        .code = BPF_ALU64 | BPF_MOV | BPF_X,
        .src_reg = BPF_REG_1,
        .dst_reg = BPF_REG_0,
    },

    /* 95 00 00 00 00 00 00 00 exit */
    {
        .code = BPF_JMP | BPF_EXIT
    },
};

இதை தாங்களே எழுதாதவர்களுக்கு ஒரு பயிற்சி - கண்டுபிடி map_fd.

எங்கள் திட்டத்தில் இன்னும் ஒரு வெளியிடப்படாத பகுதி உள்ளது - xdp_attach. துரதிர்ஷ்டவசமாக, XDP போன்ற நிரல்களை கணினி அழைப்பைப் பயன்படுத்தி இணைக்க முடியாது bpf. BPF மற்றும் XDPயை உருவாக்கியவர்கள் ஆன்லைன் லினக்ஸ் சமூகத்தைச் சேர்ந்தவர்கள், அதாவது அவர்கள் தங்களுக்கு மிகவும் பரிச்சயமான ஒன்றைப் பயன்படுத்தினர் (ஆனால் இல்லை சாதாரண மக்கள்) கர்னலுடன் தொடர்புகொள்வதற்கான இடைமுகம்: நெட்லிங்க் சாக்கெட்டுகள், மேலும் பார்க்கவும் RFC3549. செயல்படுத்த எளிதான வழி xdp_attach இலிருந்து குறியீட்டை நகலெடுக்கிறது libbpf, அதாவது, கோப்பிலிருந்து netlink.c, நாங்கள் என்ன செய்தோம், அதை சிறிது சுருக்கவும்:

நெட்லிங்க் சாக்கெட்டுகளின் உலகத்திற்கு வரவேற்கிறோம்

நெட்லிங்க் சாக்கெட் வகையைத் திறக்கவும் NETLINK_ROUTE:

int netlink_open(__u32 *nl_pid)
{
    struct sockaddr_nl sa;
    socklen_t addrlen;
    int one = 1, ret;
    int sock;

    memset(&sa, 0, sizeof(sa));
    sa.nl_family = AF_NETLINK;

    sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (sock < 0)
        err(1, "socket");

    if (setsockopt(sock, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)) < 0)
        warnx("netlink error reporting not supported");

    if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0)
        err(1, "bind");

    addrlen = sizeof(sa);
    if (getsockname(sock, (struct sockaddr *)&sa, &addrlen) < 0)
        err(1, "getsockname");

    *nl_pid = sa.nl_pid;
    return sock;
}

இந்த சாக்கெட்டிலிருந்து நாங்கள் படிக்கிறோம்:

static int bpf_netlink_recv(int sock, __u32 nl_pid, int seq)
{
    bool multipart = true;
    struct nlmsgerr *errm;
    struct nlmsghdr *nh;
    char buf[4096];
    int len, ret;

    while (multipart) {
        multipart = false;
        len = recv(sock, buf, sizeof(buf), 0);
        if (len < 0)
            err(1, "recv");

        if (len == 0)
            break;

        for (nh = (struct nlmsghdr *)buf; NLMSG_OK(nh, len);
                nh = NLMSG_NEXT(nh, len)) {
            if (nh->nlmsg_pid != nl_pid)
                errx(1, "wrong pid");
            if (nh->nlmsg_seq != seq)
                errx(1, "INVSEQ");
            if (nh->nlmsg_flags & NLM_F_MULTI)
                multipart = true;
            switch (nh->nlmsg_type) {
                case NLMSG_ERROR:
                    errm = (struct nlmsgerr *)NLMSG_DATA(nh);
                    if (!errm->error)
                        continue;
                    ret = errm->error;
                    // libbpf_nla_dump_errormsg(nh); too many code to copy...
                    goto done;
                case NLMSG_DONE:
                    return 0;
                default:
                    break;
            }
        }
    }
    ret = 0;
done:
    return ret;
}

இறுதியாக, எங்கள் செயல்பாடு இங்கே உள்ளது, இது ஒரு சாக்கெட்டைத் திறந்து, கோப்பு விளக்கத்தைக் கொண்ட சிறப்பு செய்தியை அனுப்புகிறது:

static int xdp_attach(int ifindex, int prog_fd)
{
    int sock, seq = 0, ret;
    struct nlattr *nla, *nla_xdp;
    struct {
        struct nlmsghdr  nh;
        struct ifinfomsg ifinfo;
        char             attrbuf[64];
    } req;
    __u32 nl_pid = 0;

    sock = netlink_open(&nl_pid);
    if (sock < 0)
        return sock;

    memset(&req, 0, sizeof(req));
    req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
    req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
    req.nh.nlmsg_type = RTM_SETLINK;
    req.nh.nlmsg_pid = 0;
    req.nh.nlmsg_seq = ++seq;
    req.ifinfo.ifi_family = AF_UNSPEC;
    req.ifinfo.ifi_index = ifindex;

    /* started nested attribute for XDP */
    nla = (struct nlattr *)(((char *)&req)
            + NLMSG_ALIGN(req.nh.nlmsg_len));
    nla->nla_type = NLA_F_NESTED | IFLA_XDP;
    nla->nla_len = NLA_HDRLEN;

    /* add XDP fd */
    nla_xdp = (struct nlattr *)((char *)nla + nla->nla_len);
    nla_xdp->nla_type = IFLA_XDP_FD;
    nla_xdp->nla_len = NLA_HDRLEN + sizeof(int);
    memcpy((char *)nla_xdp + NLA_HDRLEN, &prog_fd, sizeof(prog_fd));
    nla->nla_len += nla_xdp->nla_len;

    /* if user passed in any flags, add those too */
    __u32 flags = XDP_FLAGS_SKB_MODE;
    nla_xdp = (struct nlattr *)((char *)nla + nla->nla_len);
    nla_xdp->nla_type = IFLA_XDP_FLAGS;
    nla_xdp->nla_len = NLA_HDRLEN + sizeof(flags);
    memcpy((char *)nla_xdp + NLA_HDRLEN, &flags, sizeof(flags));
    nla->nla_len += nla_xdp->nla_len;

    req.nh.nlmsg_len += NLA_ALIGN(nla->nla_len);

    if (send(sock, &req, req.nh.nlmsg_len, 0) < 0)
        err(1, "send");
    ret = bpf_netlink_recv(sock, nl_pid, seq);

cleanup:
    close(sock);
    return ret;
}

எனவே, சோதனைக்கு எல்லாம் தயாராக உள்ளது:

$ cc nolibbpf.c -o nolibbpf
$ sudo strace -e bpf ./nolibbpf
bpf(BPF_MAP_CREATE, {map_type=BPF_MAP_TYPE_ARRAY, map_name="woo", ...}, 72) = 3
bpf(BPF_PROG_LOAD, {prog_type=BPF_PROG_TYPE_XDP, insn_cnt=15, prog_name="woo", ...}, 72) = 4
+++ exited with 0 +++

எங்கள் நிரல் இணைக்கப்பட்டுள்ளதா என்று பார்ப்போம் lo:

$ ip l show dev lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 xdpgeneric qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    prog/xdp id 160

பிங்ஸை அனுப்பி வரைபடத்தைப் பார்ப்போம்:

$ for s in `seq 234`; do sudo ping -f -c 100 127.0.0.1 >/dev/null 2>&1; done
$ sudo bpftool m dump name woo
key: 00 00 00 00  value: 90 01 00 00 00 00 00 00
key: 01 00 00 00  value: 00 00 00 00 00 00 00 00
key: 02 00 00 00  value: 00 00 00 00 00 00 00 00
key: 03 00 00 00  value: 00 00 00 00 00 00 00 00
key: 04 00 00 00  value: 00 00 00 00 00 00 00 00
key: 05 00 00 00  value: 00 00 00 00 00 00 00 00
key: 06 00 00 00  value: 40 b5 00 00 00 00 00 00
key: 07 00 00 00  value: 00 00 00 00 00 00 00 00
Found 8 elements

ஹர்ரே, எல்லாம் வேலை செய்கிறது. குறிப்பு, எங்கள் வரைபடம் மீண்டும் பைட்டுகள் வடிவில் காட்டப்படும். இது போலல்லாமல், என்ற உண்மையின் காரணமாகும் libbpf நாங்கள் வகை தகவலை (BTF) ஏற்றவில்லை. ஆனால் அடுத்த முறை இதைப் பற்றி மேலும் பேசுவோம்.

மேம்பாட்டு கருவிகள்

இந்தப் பிரிவில், குறைந்தபட்ச BPF டெவலப்பர் கருவித்தொகுப்பைப் பார்ப்போம்.

பொதுவாக, BPF நிரல்களை உருவாக்க உங்களுக்கு சிறப்பு எதுவும் தேவையில்லை - BPF எந்தவொரு ஒழுக்கமான விநியோக கர்னலில் இயங்குகிறது, மேலும் நிரல்களைப் பயன்படுத்தி உருவாக்கப்படுகிறது clang, இது தொகுப்பிலிருந்து வழங்கப்படலாம். இருப்பினும், பிபிஎஃப் வளர்ச்சியில் இருப்பதால், கர்னல் மற்றும் கருவிகள் தொடர்ந்து மாறிக்கொண்டே இருக்கின்றன, 2019 முதல் பழைய முறைகளைப் பயன்படுத்தி பிபிஎஃப் நிரல்களை எழுத விரும்பவில்லை என்றால், நீங்கள் தொகுக்க வேண்டும்.

  • llvm/clang
  • pahole
  • அதன் மையக்கரு
  • bpftool

(குறிப்புக்காக, இந்தப் பகுதியும் கட்டுரையில் உள்ள அனைத்து எடுத்துக்காட்டுகளும் டெபியன் 10 இல் இயக்கப்பட்டன.)

llvm/clang

BPF ஆனது LLVM உடன் நட்பாக உள்ளது மற்றும் சமீபத்தில் BPF க்கான திட்டங்கள் gcc ஐப் பயன்படுத்தி தொகுக்கப்படலாம் என்றாலும், தற்போதைய அனைத்து மேம்பாடுகளும் LLVM க்காக மேற்கொள்ளப்படுகின்றன. எனவே, முதலில், தற்போதைய பதிப்பை உருவாக்குவோம் clang git இலிருந்து:

$ sudo apt install ninja-build
$ git clone --depth 1 https://github.com/llvm/llvm-project.git
$ mkdir -p llvm-project/llvm/build/install
$ cd llvm-project/llvm/build
$ cmake .. -G "Ninja" -DLLVM_TARGETS_TO_BUILD="BPF;X86" 
                      -DLLVM_ENABLE_PROJECTS="clang" 
                      -DBUILD_SHARED_LIBS=OFF 
                      -DCMAKE_BUILD_TYPE=Release 
                      -DLLVM_BUILD_RUNTIME=OFF
$ time ninja
... много времени спустя
$

எல்லாம் சரியாகச் சேர்ந்ததா என்பதை இப்போது பார்க்கலாம்:

$ ./bin/llc --version
LLVM (http://llvm.org/):
  LLVM version 11.0.0git
  Optimized build.
  Default target: x86_64-unknown-linux-gnu
  Host CPU: znver1

  Registered Targets:
    bpf    - BPF (host endian)
    bpfeb  - BPF (big endian)
    bpfel  - BPF (little endian)
    x86    - 32-bit X86: Pentium-Pro and above
    x86-64 - 64-bit X86: EM64T and AMD64

(அசெம்பிளி வழிமுறைகள் clang என்னால் எடுக்கப்பட்டது bpf_devel_QA.)

நாங்கள் இப்போது உருவாக்கிய நிரல்களை நிறுவ மாட்டோம், மாறாக அவற்றைச் சேர்க்கவும் PATHஎடுத்துக்காட்டாக:

export PATH="`pwd`/bin:$PATH"

(இதையும் சேர்க்கலாம் .bashrc அல்லது ஒரு தனி கோப்பில். தனிப்பட்ட முறையில், நான் இதுபோன்ற விஷயங்களைச் சேர்க்கிறேன் ~/bin/activate-llvm.sh தேவைப்படும்போது நான் செய்கிறேன் . activate-llvm.sh.)

பஹோல் மற்றும் BTF

பயன்பாடு pahole BTF வடிவத்தில் பிழைத்திருத்தத் தகவலை உருவாக்க கர்னலை உருவாக்கும்போது பயன்படுத்தப்படுகிறது. BTF தொழில்நுட்பம் வசதியானது மற்றும் அதைப் பயன்படுத்த விரும்புகிறோம் என்பதைத் தவிர, அதன் விவரங்களைப் பற்றி இந்தக் கட்டுரையில் விரிவாகப் பேச மாட்டோம். எனவே நீங்கள் உங்கள் கர்னலை உருவாக்கப் போகிறீர்கள் என்றால், முதலில் உருவாக்கவும் pahole (இல்லாமல் pahole நீங்கள் விருப்பத்துடன் கர்னலை உருவாக்க முடியாது CONFIG_DEBUG_INFO_BTF:

$ git clone https://git.kernel.org/pub/scm/devel/pahole/pahole.git
$ cd pahole/
$ sudo apt install cmake
$ mkdir build
$ cd build/
$ cmake -D__LIB=lib ..
$ make
$ sudo make install
$ which pahole
/usr/local/bin/pahole

BPF உடன் பரிசோதனை செய்வதற்கான கர்னல்கள்

BPF இன் சாத்தியக்கூறுகளை ஆராயும் போது, ​​எனது சொந்த மையத்தை இணைக்க விரும்புகிறேன். பொதுவாக, இது அவசியமில்லை, ஏனெனில் நீங்கள் விநியோக கர்னலில் BPF நிரல்களைத் தொகுத்து ஏற்ற முடியும், இருப்பினும், உங்கள் சொந்த கர்னலைக் கொண்டிருப்பது சமீபத்திய BPF அம்சங்களைப் பயன்படுத்த உங்களை அனுமதிக்கிறது. , அல்லது, சில பிழைத்திருத்தக் கருவிகளைப் போலவே எதிர்காலத்தில் தொகுக்கப்படாது. மேலும், அதன் சொந்த மையமானது குறியீட்டுடன் பரிசோதனை செய்வது முக்கியமானதாக உணர வைக்கிறது.

ஒரு கர்னலை உருவாக்க, முதலில், கர்னலும், இரண்டாவதாக, ஒரு கர்னல் உள்ளமைவுக் கோப்பும் தேவை. BPF உடன் பரிசோதனை செய்ய நாம் வழக்கமானதைப் பயன்படுத்தலாம் வெண்ணிலா கர்னல் அல்லது வளர்ச்சி கர்னல்களில் ஒன்று. வரலாற்று ரீதியாக, பிபிஎஃப் மேம்பாடு லினக்ஸ் நெட்வொர்க்கிங் சமூகத்திற்குள் நடைபெறுகிறது, எனவே அனைத்து மாற்றங்களும் விரைவில் அல்லது பின்னர் லினக்ஸ் நெட்வொர்க்கிங் பராமரிப்பாளரான டேவிட் மில்லர் மூலம் செல்கின்றன. அவற்றின் இயல்பைப் பொறுத்து - திருத்தங்கள் அல்லது புதிய அம்சங்கள் - பிணைய மாற்றங்கள் இரண்டு கோர்களில் ஒன்று - net அல்லது net-next. BPF க்கான மாற்றங்கள் இடையே அதே வழியில் விநியோகிக்கப்படுகின்றன bpf и bpf-next, அவை முறையே நெட் மற்றும் நெட்-அடுத்ததாக இணைக்கப்படுகின்றன. மேலும் விவரங்களுக்கு, பார்க்கவும் bpf_devel_QA и netdev-FAQ. எனவே உங்கள் சுவை மற்றும் நீங்கள் சோதிக்கும் கணினியின் நிலைத்தன்மை தேவைகளின் அடிப்படையில் ஒரு கர்னலைத் தேர்ந்தெடுக்கவும் (*-next பட்டியலிடப்பட்டவற்றில் கர்னல்கள் மிகவும் நிலையற்றவை).

கர்னல் உள்ளமைவு கோப்புகளை எவ்வாறு நிர்வகிப்பது என்பது பற்றி பேசுவது இந்த கட்டுரையின் எல்லைக்கு அப்பாற்பட்டது - இதை எப்படி செய்வது என்பது உங்களுக்கு ஏற்கனவே தெரியும் என்று கருதப்படுகிறது, அல்லது கற்றுக்கொள்ள தயார் சொந்தமாக. இருப்பினும், BPF-இயக்கப்பட்ட அமைப்பை உங்களுக்கு வழங்குவதற்கு பின்வரும் வழிமுறைகள் அதிகமாகவோ அல்லது குறைவாகவோ இருக்க வேண்டும்.

மேலே உள்ள கர்னல்களில் ஒன்றைப் பதிவிறக்கவும்:

$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git
$ cd bpf-next

குறைந்தபட்ச வேலை செய்யும் கர்னல் கட்டமைப்பை உருவாக்கவும்:

$ cp /boot/config-`uname -r` .config
$ make localmodconfig

கோப்பில் BPF விருப்பங்களை இயக்கவும் .config உங்கள் சொந்த விருப்பப்படி (பெரும்பாலும் CONFIG_BPF systemd பயன்படுத்துவதால் ஏற்கனவே இயக்கப்படும்). இந்த கட்டுரைக்கு பயன்படுத்தப்படும் கர்னலில் இருந்து விருப்பங்களின் பட்டியல் இங்கே:

CONFIG_CGROUP_BPF=y
CONFIG_BPF=y
CONFIG_BPF_LSM=y
CONFIG_BPF_SYSCALL=y
CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_JIT_DEFAULT_ON=y
CONFIG_IPV6_SEG6_BPF=y
# CONFIG_NETFILTER_XT_MATCH_BPF is not set
# CONFIG_BPFILTER is not set
CONFIG_NET_CLS_BPF=y
CONFIG_NET_ACT_BPF=y
CONFIG_BPF_JIT=y
CONFIG_BPF_STREAM_PARSER=y
CONFIG_LWTUNNEL_BPF=y
CONFIG_HAVE_EBPF_JIT=y
CONFIG_BPF_EVENTS=y
CONFIG_BPF_KPROBE_OVERRIDE=y
CONFIG_DEBUG_INFO_BTF=y

பின்னர் நாம் தொகுதிகள் மற்றும் கர்னலை எளிதாக அசெம்பிள் செய்து நிறுவலாம் (இதன் மூலம், நீங்கள் புதிதாக கூடியிருப்பதைப் பயன்படுத்தி கர்னலை அசெம்பிள் செய்யலாம். clangசேர்ப்பதன் மூலம் CC=clang):

$ make -s -j $(getconf _NPROCESSORS_ONLN)
$ sudo make modules_install
$ sudo make install

புதிய கர்னலுடன் மறுதொடக்கம் செய்யுங்கள் (இதற்கு நான் பயன்படுத்துகிறேன் kexec தொகுப்பிலிருந்து kexec-tools):

v=5.8.0-rc6+ # если вы пересобираете текущее ядро, то можно делать v=`uname -r`
sudo kexec -l -t bzImage /boot/vmlinuz-$v --initrd=/boot/initrd.img-$v --reuse-cmdline &&
sudo kexec -e

bpftool

கட்டுரையில் பொதுவாகப் பயன்படுத்தப்படும் பயன்பாடானது பயன்பாடாகும் bpftool, லினக்ஸ் கர்னலின் ஒரு பகுதியாக வழங்கப்படுகிறது. இது BPF டெவலப்பர்களுக்காக BPF டெவலப்பர்களால் எழுதப்பட்டு பராமரிக்கப்படுகிறது மற்றும் அனைத்து வகையான BPF பொருட்களையும் நிர்வகிக்கப் பயன்படுகிறது - நிரல்களை ஏற்றுதல், வரைபடங்களை உருவாக்குதல் மற்றும் திருத்துதல், BPF சுற்றுச்சூழல் அமைப்பின் வாழ்க்கையை ஆராய்தல் போன்றவை. மேன் பக்கங்களுக்கான மூலக் குறியீடுகள் வடிவில் உள்ள ஆவணங்களைக் காணலாம் மையத்தில் அல்லது, ஏற்கனவே தொகுக்கப்பட்ட, நிகழ்நிலை.

இதை எழுதும் நேரத்தில் bpftool RHEL, Fedora மற்றும் Ubuntu க்கு மட்டுமே தயாராக உள்ளது (எடுத்துக்காட்டாக, பார்க்கவும் இந்த நூல், இது பேக்கேஜிங்கின் முடிக்கப்படாத கதையைச் சொல்கிறது bpftool டெபியனில்). ஆனால் நீங்கள் ஏற்கனவே உங்கள் கர்னலை உருவாக்கியிருந்தால், பின்னர் உருவாக்கவும் bpftool பை போல எளிதானது:

$ cd ${linux}/tools/bpf/bpftool
# ... пропишите пути к последнему clang, как рассказано выше
$ make -s

Auto-detecting system features:
...                        libbfd: [ on  ]
...        disassembler-four-args: [ on  ]
...                          zlib: [ on  ]
...                        libcap: [ on  ]
...               clang-bpf-co-re: [ on  ]

Auto-detecting system features:
...                        libelf: [ on  ]
...                          zlib: [ on  ]
...                           bpf: [ on  ]

$

(இங்கே ${linux} - இது உங்கள் கர்னல் கோப்பகம்.) இந்த கட்டளைகளை இயக்கிய பிறகு bpftool ஒரு கோப்பகத்தில் சேகரிக்கப்படும் ${linux}/tools/bpf/bpftool மேலும் அதை பாதையில் சேர்க்கலாம் (முதலில் பயனருக்கு root) அல்லது நகலெடுக்கவும் /usr/local/sbin.

திரட்டுதல் bpftool பிந்தையதைப் பயன்படுத்துவது நல்லது clang, மேலே விவரிக்கப்பட்டபடி கூடியது, அது சரியாக கூடியிருக்கிறதா என்பதைச் சரிபார்க்கவும் - எடுத்துக்காட்டாக, கட்டளையைப் பயன்படுத்தி

$ sudo bpftool feature probe kernel
Scanning system configuration...
bpf() syscall for unprivileged users is enabled
JIT compiler is enabled
JIT compiler hardening is disabled
JIT compiler kallsyms exports are enabled for root
...

உங்கள் கர்னலில் எந்த BPF அம்சங்கள் இயக்கப்பட்டுள்ளன என்பதைக் காட்டும்.

மூலம், முந்தைய கட்டளையை இயக்கலாம்

# bpftool f p k

தொகுப்பிலிருந்து வரும் பயன்பாடுகளுடன் ஒப்புமை மூலம் இது செய்யப்படுகிறது iproute2, நாம் எங்கே, உதாரணமாக, சொல்ல முடியும் ip a s eth0 அதற்கு பதிலாக ip addr show dev eth0.

முடிவுக்கு

BPF ஆனது ஒரு பிளேவை திறம்பட அளவிடுவதற்கும், மையத்தின் செயல்பாட்டை மாற்றுவதற்கும் உங்களை அனுமதிக்கிறது. UNIX இன் சிறந்த மரபுகளில், கணினி மிகவும் வெற்றிகரமாக மாறியது: கர்னலை (மீண்டும்) நிரல்படுத்த உங்களை அனுமதிக்கும் ஒரு எளிய பொறிமுறையானது அதிக எண்ணிக்கையிலான நபர்களையும் நிறுவனங்களையும் பரிசோதனை செய்ய அனுமதித்தது. மேலும், சோதனைகள், அத்துடன் பிபிஎஃப் உள்கட்டமைப்பின் மேம்பாடு ஆகியவை முடிவடையவில்லை என்றாலும், கணினி ஏற்கனவே நிலையான ஏபிஐயைக் கொண்டுள்ளது, இது நம்பகமான மற்றும் மிக முக்கியமாக பயனுள்ள வணிக தர்க்கத்தை உருவாக்க உங்களை அனுமதிக்கிறது.

என் கருத்துப்படி, தொழில்நுட்பம் மிகவும் பிரபலமாகிவிட்டது என்பதை நான் கவனிக்க விரும்புகிறேன், ஏனெனில், ஒருபுறம், அது முடியும் விளையாட (ஒரு இயந்திரத்தின் கட்டமைப்பை ஒரு மாலை நேரத்தில் அதிகமாகவோ அல்லது குறைவாகவோ புரிந்து கொள்ள முடியும்), மறுபுறம், அதன் தோற்றத்திற்கு முன் (அழகாக) தீர்க்க முடியாத சிக்கல்களைத் தீர்க்கவும். இந்த இரண்டு கூறுகளும் சேர்ந்து மக்களை பரிசோதனை மற்றும் கனவு காண கட்டாயப்படுத்துகின்றன, இது மேலும் மேலும் புதுமையான தீர்வுகளின் தோற்றத்திற்கு வழிவகுக்கிறது.

இந்தக் கட்டுரை, குறிப்பாகச் சுருக்கமாக இல்லாவிட்டாலும், BPF உலகிற்கு ஒரு அறிமுகம் மட்டுமே மற்றும் "மேம்பட்ட" அம்சங்கள் மற்றும் கட்டிடக்கலையின் முக்கிய பகுதிகளை விவரிக்கவில்லை. முன்னோக்கி செல்லும் திட்டம் இது போன்றது: அடுத்த கட்டுரை BPF நிரல் வகைகளின் மேலோட்டமாக இருக்கும் (5.8 கர்னலில் 30 நிரல் வகைகள் ஆதரிக்கப்படுகின்றன), பின்னர் கர்னல் டிரேசிங் நிரல்களைப் பயன்படுத்தி உண்மையான BPF பயன்பாடுகளை எவ்வாறு எழுதுவது என்பதைப் பார்ப்போம். எடுத்துக்காட்டாக, BPF கட்டமைப்பு மற்றும் பாதுகாப்பு பயன்பாடுகளின் எடுத்துக்காட்டுகளைத் தொடர்ந்து BPF கட்டமைப்பைப் பற்றிய ஆழமான பாடத்திற்கான நேரம் இது.

இந்தத் தொடரின் முந்தைய கட்டுரைகள்

  1. சிறியவர்களுக்கான BPF, பகுதி பூஜ்யம்: கிளாசிக் BPF

இணைப்புகள்

  1. BPF மற்றும் XDP குறிப்பு வழிகாட்டி — சிலியம் இருந்து BPF ஆவணங்கள், அல்லது இன்னும் துல்லியமாக BPF உருவாக்கியவர்கள் மற்றும் பராமரிப்பாளர்களில் ஒருவரான டேனியல் போர்க்மேனிடமிருந்து. இது முதல் தீவிரமான விளக்கங்களில் ஒன்றாகும், இது மற்றவர்களிடமிருந்து வேறுபட்டது, டேனியல் அவர் எதைப் பற்றி எழுதுகிறார் என்பதை சரியாக அறிந்திருக்கிறார், அதில் எந்த தவறும் இல்லை. குறிப்பாக, நன்கு அறியப்பட்ட பயன்பாட்டைப் பயன்படுத்தி XDP மற்றும் TC வகைகளின் BPF நிரல்களுடன் எவ்வாறு வேலை செய்வது என்பதை இந்த ஆவணம் விவரிக்கிறது. ip தொகுப்பிலிருந்து iproute2.

  2. ஆவணப்படுத்தல்/நெட்வொர்க்கிங்/filter.txt — கிளாசிக் மற்றும் பின்னர் நீட்டிக்கப்பட்ட BPFக்கான ஆவணங்களுடன் அசல் கோப்பு. நீங்கள் சட்டசபை மொழி மற்றும் தொழில்நுட்ப கட்டிடக்கலை விவரங்களை ஆராய விரும்பினால் ஒரு நல்ல வாசிப்பு.

  3. முகநூலில் இருந்து BPF பற்றிய வலைப்பதிவு. அலெக்ஸி ஸ்டாரோவொய்டோவ் (ஈபிபிஎஃப் ஆசிரியர்) மற்றும் ஆண்ட்ரி நக்ரிகோ - (பராமரிப்பவர்) எழுதுவது போல் இது அரிதாகவே புதுப்பிக்கப்படுகிறது, ஆனால் பொருத்தமாக உள்ளது. libbpf).

  4. bpftool இன் ரகசியங்கள். க்வென்டின் மோனட்டிலிருந்து ஒரு பொழுதுபோக்கு ட்விட்டர் நூல், bpftool ஐப் பயன்படுத்துவதற்கான எடுத்துக்காட்டுகள் மற்றும் ரகசியங்கள்.

  5. BPF இல் முழுக்கு: படிக்கும் பொருட்களின் பட்டியல். குவென்டின் மோனெட்டிலிருந்து BPF ஆவணங்களுக்கான இணைப்புகளின் மாபெரும் (இன்னும் பராமரிக்கப்படுகிறது) பட்டியல்.

ஆதாரம்: www.habr.com

கருத்தைச் சேர்