መጀመሪያ ላይ ቴክኖሎጂ ነበረ እና BPF ተብሎ ይጠራ ነበር. ተመለከትናት
በግምት፣ BPF በዘፈቀደ በተጠቃሚ የቀረበ ኮድ በሊኑክስ ከርነል ቦታ ላይ እንዲያሄዱ ይፈቅድልዎታል፣ እና አዲሱ አርክቴክቸር በጣም የተሳካ ሆኖ ስለተገኘ ሁሉንም አፕሊኬሽኖቹን ለመግለጽ ደርዘን ተጨማሪ ጽሑፎች እንፈልጋለን። (ከዚህ በታች ባለው የአፈጻጸም ኮድ ላይ እንደምታዩት ገንቢዎቹ ጥሩ ያላደረጉት ብቸኛው ነገር ጥሩ አርማ መፍጠር ነበር።)
ይህ ጽሑፍ የBPF ቨርቹዋል ማሽንን አወቃቀር፣ ከ BPF ጋር ለመስራት የከርነል መገናኛዎች፣ የልማት መሳሪያዎች፣ እንዲሁም ስለ ነባር ችሎታዎች አጭር፣ በጣም አጭር አጠቃላይ እይታን ያብራራል። የ BPF ተግባራዊ አተገባበርን በጥልቀት ለማጥናት ወደፊት የሚያስፈልገንን ሁሉ።
የጽሁፉ ማጠቃለያ
bpf(2)
.
Пишем программы BPF с помощью libbpf
.libbpf
. በሚቀጥሉት ምሳሌዎች የምንጠቀመው መሰረታዊ የ BPF መተግበሪያ አጽም እንፈጥራለን።
የ BPF አርክቴክቸር መግቢያ
የBPF አርክቴክቸርን ማጤን ከመጀመራችን በፊት፣ ለመጨረሻ ጊዜ (ኦህ) እንጠቅሳለን።
አዲሱ BPF የተገነባው ለ64 ቢት ማሽኖች፣ ለደመና አገልግሎቶች እና ኤስዲኤንን ለመፍጠር ለሚፈልጉ መሳሪያዎች ፍላጎት ምላሽ ለመስጠት ነው።Sኦቨርትዌር -dተጣራ nሥራ መሥራት)። በከርነል ኔትወርክ መሐንዲሶች የተገነባው ለጥንታዊው BPF ምትክ ፣ አዲሱ BPF በትክክል ከስድስት ወራት በኋላ የሊኑክስ ስርዓቶችን የመከታተል ከባድ ሥራ ውስጥ አፕሊኬሽኖችን አገኘ ፣ እና አሁን ፣ ከታየ ከስድስት ዓመታት በኋላ ፣ የሚቀጥለው ጽሑፍ እንፈልጋለን ። የተለያዩ የፕሮግራሞችን ዓይነቶች ይዘርዝሩ።
አስቂኝ ስዕሎች
በዋናው ላይ፣ BPF ደህንነትን ሳይጎዳ በከርነል ቦታ ላይ “የዘፈቀደ” ኮድ እንዲያሄዱ የሚያስችልዎ ማጠሪያ ምናባዊ ማሽን ነው። BPF ፕሮግራሞች በተጠቃሚ ቦታ ውስጥ ተፈጥረዋል፣ ወደ ከርነል ተጭነዋል እና ከአንዳንድ የክስተት ምንጭ ጋር የተገናኙ ናቸው። አንድ ክስተት ለምሳሌ ፓኬትን ወደ አውታረ መረብ በይነገጽ ማድረስ፣ የከርነል ተግባር መጀመር፣ ወዘተ ሊሆን ይችላል። በጥቅል ሁኔታ የ BPF ፕሮግራም የጥቅሉን ውሂብ እና ሜታዳታ (ለማንበብ እና ምናልባትም ለመጻፍ እንደ ፕሮግራሙ ዓይነት) የከርነል ተግባርን በሚሠራበት ጊዜ ክርክሮች ተግባሩ፣ የከርነል ማህደረ ትውስታ ጠቋሚዎችን ጨምሮ፣ ወዘተ.
ይህን ሂደት በጥልቀት እንመልከተው። ለመጀመር፣ ከጥንታዊው BPF ስለ መጀመሪያው ልዩነት እንነጋገር፣ በተሰብሳቢ ውስጥ የተፃፉ ፕሮግራሞች። በአዲሱ እትም ኘሮግራሞች በከፍተኛ ደረጃ ቋንቋዎች እንዲፃፉ አርክቴክቸር ተዘርግቷል፣ በዋነኛነት፣ በ C. ለዚህ፣ ለኤልቪኤም የጀርባ ገፅ ተዘጋጅቷል፣ ይህም ለ BPF አርክቴክቸር ባይትኮድ እንዲያመነጩ የሚያስችል ነው።
የBPF አርክቴክቸር የተነደፈው በከፊል በዘመናዊ ማሽኖች ላይ በብቃት እንዲሠራ ነው። ይህንን በተግባር ለመስራት፣ BPF ባይት ኮድ፣ አንዴ ወደ ከርነል ከተጫነ፣ JIT compiler የሚባል አካል በመጠቀም ወደ ቤተኛ ኮድ ተተርጉሟል።Just In Tኢሜ)። በመቀጠል ፣ ካስታወሱ ፣ በሚታወቀው BPF ፕሮግራሙ ወደ ከርነል ተጭኖ ከዝግጅቱ ምንጭ ጋር በአቶሚክ ተያይዟል - በአንድ የስርዓት ጥሪ አውድ ውስጥ። በአዲሱ አርክቴክቸር, ይህ በሁለት ደረጃዎች ይከናወናል - በመጀመሪያ, ኮዱ የስርዓት ጥሪን በመጠቀም ወደ ከርነል ይጫናል. bpf(2)
እና ከዚያ በኋላ, እንደ መርሃግብሩ አይነት በሚለያዩ ሌሎች ዘዴዎች, ፕሮግራሙ ከዝግጅቱ ምንጭ ጋር ይያያዛል.
እዚህ አንባቢው አንድ ጥያቄ ሊኖረው ይችላል: ይቻል ነበር? የእንደዚህ አይነት ኮድ አፈፃፀም ደህንነት እንዴት ይረጋገጣል? የማስፈጸሚያ ደህንነት ዋስትና ተሰጥቶናል አረጋጋጭ በሚባሉ የ BPF ፕሮግራሞችን የመጫን ደረጃ (በእንግሊዘኛ ይህ ደረጃ አረጋጋጭ ይባላል እና የእንግሊዝኛውን ቃል መጠቀሜን እቀጥላለሁ)
አረጋጋጭ አንድ ፕሮግራም መደበኛውን የከርነል አሠራር እንደማይረብሽ የሚያረጋግጥ የማይንቀሳቀስ ተንታኝ ነው። ይህ በነገራችን ላይ መርሃግብሩ በስርዓቱ አሠራር ውስጥ ጣልቃ መግባት አይችልም ማለት አይደለም - የ BPF ፕሮግራሞች እንደ ዓይነቱ ላይ በመመስረት የከርነል ማህደረ ትውስታ ክፍሎችን ማንበብ እና መፃፍ ፣ የተግባር እሴቶችን መመለስ ፣ ማሳጠር ፣ ማያያዝ ፣ እንደገና መፃፍ ይችላሉ ። እና የአውታረ መረብ እሽጎችን እንኳን ያስተላልፉ። አረጋጋጭ የBPF ፕሮግራምን ማስኬድ ከርነል እንደማይበላሽ እና በህጉ መሰረት የፅሁፍ መዳረሻ ያለው ፕሮግራም ለምሳሌ የወጪ ፓኬት መረጃ ከፓኬቱ ውጭ የከርነል ማህደረ ትውስታን መፃፍ እንደማይችል ዋስትና ይሰጣል። ከሌሎች የ BPF አካላት ጋር ካወቅን በኋላ አረጋጋጭን በተዛማጅ ክፍል ውስጥ በጥቂቱ በዝርዝር እንመለከታለን።
ታዲያ እስካሁን ምን ተማርን? ተጠቃሚው በ C ውስጥ አንድ ፕሮግራም ይጽፋል, የስርዓት ጥሪን በመጠቀም ወደ ከርነል ይጭናል bpf(2)
፣ በአረጋጋጭ የተረጋገጠ እና ወደ ቤተኛ ባይት ኮድ የተተረጎመ። ከዚያ ተመሳሳይ ወይም ሌላ ተጠቃሚ ፕሮግራሙን ከዝግጅቱ ምንጭ ጋር ያገናኘዋል እና መፈጸም ይጀምራል. ቡት እና ግንኙነትን መለየት ለብዙ ምክንያቶች አስፈላጊ ነው. በመጀመሪያ አረጋጋጭን ማስኬድ በአንጻራዊነት ውድ ነው እና ተመሳሳይ ፕሮግራም ብዙ ጊዜ በማውረድ የኮምፒተር ጊዜን እናጠፋለን። በሁለተኛ ደረጃ, አንድ ፕሮግራም በትክክል እንዴት እንደሚገናኝ በአይነቱ ላይ የተመሰረተ ነው, እና ከአንድ አመት በፊት የተሰራ አንድ "ሁለንተናዊ" በይነገጽ ለአዳዲስ ፕሮግራሞች ተስማሚ ላይሆን ይችላል. (አሁን አርክቴክቸር የበለጠ ብስለት እየሆነ ቢመጣም ይህንን በይነገጽ በደረጃ አንድ ለማድረግ ሀሳብ አለ። libbpf
.)
በትኩረት የሚከታተለው አንባቢ በሥዕሎቹ ላይ ገና እንዳልጨረስን ሊያስተውል ይችላል። በእርግጥ፣ ከላይ ያሉት ሁሉም BPF ከጥንታዊ BPF ጋር ሲወዳደር ምስሉን በመሠረታዊነት የሚቀይርበትን ምክንያት አያብራራም። የተግባራዊነትን ወሰን በእጅጉ የሚያሰፉ ሁለት ፈጠራዎች የጋራ ማህደረ ትውስታ እና የከርነል አጋዥ ተግባራትን የመጠቀም ችሎታ ናቸው። በ BPF ውስጥ የጋራ ማህደረ ትውስታ ካርታዎች የሚባሉትን በመጠቀም ተተግብሯል - የጋራ የውሂብ አወቃቀሮችን ከተወሰነ ኤፒአይ ጋር። ምናልባት ይህን ስም ያገኙት የመጀመሪያው የካርታ አይነት የሃሽ ጠረጴዛ ስለነበር ነው። ከዚያም ድርድሮች ታዩ፣ የሀገር ውስጥ (በሲፒዩ) የሃሽ ጠረጴዛዎች እና የአካባቢ ድርድሮች፣ የፍለጋ ዛፎች፣ የ BPF ፕሮግራሞች ጠቋሚዎችን የያዙ ካርታዎች እና ሌሎችም። አሁን ለእኛ የሚያስደንቀን የBPF ፕሮግራሞች በጥሪዎች መካከል ያለውን ሁኔታ የመቀጠል እና ለሌሎች ፕሮግራሞች እና ከተጠቃሚ ቦታ ጋር የመጋራት ችሎታ መቻላቸው ነው።
ካርታዎች የስርዓት ጥሪን በመጠቀም ከተጠቃሚ ሂደቶች ይደርሳሉ bpf(2)
, እና የረዳት ተግባራትን በመጠቀም በከርነል ውስጥ ከሚሰሩ የ BPF ፕሮግራሞች. ከዚህም በላይ ረዳቶች ከካርታዎች ጋር ለመስራት ብቻ ሳይሆን ሌሎች የከርነል ችሎታዎችን ለማግኘትም አሉ. ለምሳሌ፣ BPF ፕሮግራሞች እሽጎችን ወደ ሌሎች በይነገጾች ለማስተላለፍ፣ የፐርፍ ክስተቶችን ለማመንጨት፣ የከርነል አወቃቀሮችን ለመድረስ እና የመሳሰሉትን ለማድረግ የረዳት ተግባራትን መጠቀም ይችላሉ።
ለማጠቃለል፣ BPF የዘፈቀደ፣ ማለትም፣ አረጋጋጭ የተፈተነ፣ የተጠቃሚ ኮድ ወደ የከርነል ቦታ የመጫን ችሎታ ይሰጣል። ይህ ኮድ በጥሪዎች መካከል ሁኔታን መቆጠብ እና ከተጠቃሚ ቦታ ጋር ውሂብ መለዋወጥ እና እንዲሁም በዚህ አይነት ፕሮግራም የተፈቀዱ የከርነል ንዑስ ስርዓቶችን ማግኘት ይችላል።
ይህ ቀድሞውኑ በከርነል ሞጁሎች ከሚሰጡት ችሎታዎች ጋር ተመሳሳይ ነው ፣ ከዚህ ጋር ሲነፃፀር BPF አንዳንድ ጥቅሞች አሉት (በእርግጥ ፣ ተመሳሳይ መተግበሪያዎችን ብቻ ማወዳደር ይችላሉ ፣ ለምሳሌ ፣ የስርዓት ፍለጋ - የዘፈቀደ ሹፌር ከ BPF ጋር መፃፍ አይችሉም)። ዝቅተኛ የመግቢያ ገደብ (BPF ን የሚጠቀሙ አንዳንድ መገልገያዎች ተጠቃሚው የከርነል ፕሮግራሚንግ ክህሎት ወይም በአጠቃላይ የፕሮግራም አወጣጥ ችሎታን አይጠይቁም) ፣ የሩታይም ደህንነት (በሚጽፉበት ጊዜ ስርዓቱን ላልጣሱ ሰዎች በአስተያየቶች ውስጥ እጃችሁን አንሱ) ልብ ይበሉ። ወይም ሙከራ ሞጁሎች), atomity - ሞጁሎች ዳግም ሲጫን ጊዜ ማጣት አለ, እና BPF subsystem ምንም ክስተቶች ያመለጡ መሆኑን ያረጋግጣል (ፍትሃዊ መሆን, ይህ BPF ፕሮግራሞች ሁሉም ዓይነቶች የሚሆን እውነት አይደለም).
እንዲህ ያሉ ችሎታዎች መገኘት BPF ዩኒቨርሳል መሣሪያ ከርነል ለማስፋፋት ያደርገዋል, ይህም በተግባር የተረጋገጠ ነው: ፕሮግራሞች ተጨማሪ እና ተጨማሪ አዲስ አይነቶች BPF ታክሏል, ተጨማሪ እና ተጨማሪ ትላልቅ ኩባንያዎች BPF ፍልሚያ አገልጋዮች 24 × 7 ላይ, ተጨማሪ እና ተጨማሪ ይጠቀማሉ. ጀማሪዎች ሥራቸውን የሚገነቡት በ BPF ላይ በተመሰረቱ መፍትሄዎች ላይ ነው። BPF በሁሉም ቦታ ጥቅም ላይ ይውላል: ከ DDoS ጥቃቶች ለመጠበቅ, SDN መፍጠር (ለምሳሌ, ለ kubernetes አውታረ መረቦችን መተግበር), እንደ ዋናው የስርዓት መፈለጊያ መሳሪያ እና ስታቲስቲክስ ሰብሳቢ, በጠለፋ ማወቂያ ስርዓቶች እና ማጠሪያ ስርዓቶች, ወዘተ.
የጽሁፉን አጠቃላይ እይታ እዚህ ላይ እንጨርስ እና ቨርቹዋል ማሽንን እና የቢፒኤፍ ስነ-ምህዳርን በበለጠ ዝርዝር እንመልከት።
Digression: መገልገያዎች
ምሳሌዎችን በሚቀጥሉት ክፍሎች ለማስኬድ እንዲችሉ ፣ቢያንስ ብዙ መገልገያዎችን ያስፈልግዎታል llvm
/clang
በ bpf ድጋፍ እና bpftool
... በክፍል ውስጥ
BPF ምናባዊ ማሽን መመዝገቢያ እና መመሪያ ስርዓት
የBPF አርክቴክቸር እና የትእዛዝ ስርዓት ፕሮግራሞች በC ቋንቋ እንደሚፃፉ እና ወደ ከርነል ከተጫነ በኋላ ወደ ቤተኛ ኮድ መተርጎሙን ከግምት ውስጥ በማስገባት ተዘጋጅተዋል። ስለዚህ, የመመዝገቢያዎች ቁጥር እና የትዕዛዝ ስብስቦች በዘመናዊ ማሽኖች አቅም በሂሳብ አቆጣጠር ወደ መገናኛው በአይን ተመርጠዋል. በተጨማሪም በፕሮግራሞች ላይ የተለያዩ እገዳዎች ተጥለዋል ለምሳሌ እስከ ቅርብ ጊዜ ድረስ loops እና subroutines መጻፍ አልተቻለም እና የመመሪያዎቹ ብዛት በ 4096 ብቻ ተወስኗል (አሁን ልዩ ልዩ ፕሮግራሞች እስከ አንድ ሚሊዮን መመሪያዎችን ሊጫኑ ይችላሉ).
BPF አስራ አንድ ለተጠቃሚ ተደራሽ የሆኑ 64-ቢት መዝገቦች አሉት r0
-r10
እና የፕሮግራም ቆጣሪ. ይመዝገቡ r10
ፍሬም ጠቋሚ ይዟል እና ተነባቢ-ብቻ ነው። ፕሮግራሞች በአሂድ ጊዜ 512-ባይት ቁልል እና ያልተገደበ የተጋራ ማህደረ ትውስታ በካርታ መልክ ማግኘት ይችላሉ።
BPF ፕሮግራሞች የተወሰኑ የፕሮግራም አይነት የከርነል አጋዥዎችን እና በቅርቡ ደግሞ መደበኛ ተግባራትን እንዲያሄዱ ተፈቅዶላቸዋል። እያንዳንዱ ተግባር ተብሎ የሚጠራው እስከ አምስት ክርክሮች ሊወስድ ይችላል, በመመዝገቢያ ውስጥ ያልፋል r1
-r5
, እና የመመለሻ ዋጋው ወደ ተላልፏል r0
. ከተግባሩ ከተመለሰ በኋላ የመመዝገቢያዎቹ ይዘቶች ዋስትና ተሰጥቶታል r6
-r9
አይለወጥም።
ቀልጣፋ ፕሮግራም ትርጉም ለማግኘት, ይመዘግባል r0
-r11
ለሁሉም የሚደገፉ አርክቴክቸሮች አሁን ያለውን የአርክቴክቸር ገፅታዎች ከግምት ውስጥ በማስገባት ለእውነተኛ መዝገቦች በተለየ ካርታ ተዘጋጅተዋል። ለምሳሌ ለ 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
struct __sk_buff
struct pt_regs
ስለዚህ፣ የተመዝጋቢዎች ስብስብ፣ የከርነል አጋዥዎች፣ ቁልል፣ የአውድ ጠቋሚ እና የጋራ ማህደረ ትውስታ በካርታዎች መልክ ነበረን። ይህ ሁሉ በጉዞው ላይ የግድ አስፈላጊ ነው ማለት አይደለም, ነገር ግን ...
መግለጫውን እንቀጥል እና ከእነዚህ ነገሮች ጋር ለመስራት ስለ ትዕዛዝ ስርዓት እንነጋገር. ሁሉም (
ይህ ነው Code
- ይህ የመመሪያው ኢንኮዲንግ ነው ፣ Dst
/Src
በቅደም ተከተል የተቀባዩ እና ምንጭ ኢንኮዲንግ ናቸው ፣ Off
- 16-ቢት የተፈረመ ገብ፣ እና Imm
በአንዳንድ መመሪያዎች ውስጥ ጥቅም ላይ የዋለ ባለ 32-ቢት የተፈረመ ኢንቲጀር ነው (ከሲቢፒኤፍ ቋሚ ኬ ጋር ተመሳሳይ)። ኢንኮዲንግ Code
ከሁለት ዓይነቶች አንዱ አለው:
የትምህርት ክፍሎች 0 ፣ 1 ፣ 2 ፣ 3 ከማህደረ ትውስታ ጋር ለመስራት ትዕዛዞችን ይገልፃሉ። እነሱ BPF_LD
, BPF_LDX
, BPF_ST
, BPF_STX
, በቅደም ተከተል. ክፍል 4፣ 7BPF_ALU
, BPF_ALU64
) የ ALU መመሪያዎች ስብስብ ይመሰርታል። ክፍል 5፣ 6BPF_JMP
, BPF_JMP32
) የመዝለል መመሪያዎችን ይዟል።
የ BPF መመሪያ ስርዓትን ለማጥናት የሚቀጥለው እቅድ እንደሚከተለው ነው-ሁሉንም መመሪያዎች እና መመዘኛዎቻቸውን በጥንቃቄ ከመዘርዘር ይልቅ በዚህ ክፍል ውስጥ ሁለት ምሳሌዎችን እንመለከታለን እና ከነሱ መመሪያው እንዴት እንደሚሰራ እና እንዴት እንደሚሰራ ግልጽ ይሆናል. ለ BPF ማንኛውንም ሁለትዮሽ ፋይል በእጅ ይንጠቁ። በጽሁፉ ውስጥ ያለውን ይዘት ለማዋሃድ፣ ስለ አረጋጋጭ፣ ጂአይቲ ማቀናበሪያ፣ የጥንታዊ BPF ትርጉም እና እንዲሁም ካርታዎችን ስናጠና፣ የመደወያ ተግባራትን እና የመሳሰሉትን በተናጥል መመሪያዎችን እንገናኛለን።
ስለ ግለሰባዊ መመሪያዎች ስንነጋገር, ዋናዎቹን ፋይሎች እንጠቅሳለን bpf.h
bpf_common.h
ምሳሌ፡- 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
ነው s
(ምንጭ), ከዚያም እሴቱ ከምንጩ መመዝገቢያ ይወሰዳል, እና እንደእኛ ሁኔታ, ካልተዋቀረ, እሴቱ ከእርሻው ይወሰዳል. Imm
. ስለዚህ በመጀመሪያው እና በሶስተኛው መመሪያ ውስጥ ቀዶ ጥገናውን እናከናውናለን r0 = Imm
. በተጨማሪም የ JMP ክፍል 1 አሠራር ነው 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_PROG_LOAD
и BPF_MAP_CREATE
የስርዓት ጥሪ bpf(2)
ይህ እንዴት እንደሚከሰት በሚቀጥለው ክፍል እንነጋገራለን ። ይህ የከርነል መረጃ አወቃቀሮችን እና ለእያንዳንዳቸው ይፈጥራል refcount
(የማጣቀሻ ቆጠራ) ወደ አንድ ተቀናብሯል እና ነገሩን የሚያመለክት የፋይል ገላጭ ለተጠቃሚው ይመለሳል። መያዣው ከተዘጋ በኋላ refcount
እቃው በአንድ ይቀንሳል, እና ወደ ዜሮ ሲደርስ እቃው ይደመሰሳል.
ፕሮግራሙ ካርታዎችን ከተጠቀመ, ከዚያ refcount
እነዚህ ካርታዎች ፕሮግራሙን ከጫኑ በኋላ በአንድ ይጨምራሉ, ማለትም. የፋይል ገላጭዎቻቸው ከተጠቃሚው ሂደት እና አሁንም ሊዘጉ ይችላሉ refcount
ዜሮ አይሆንም
አንድን ፕሮግራም በተሳካ ሁኔታ ከጫንን በኋላ ብዙውን ጊዜ ወደ አንድ ዓይነት የዝግጅት ጀነሬተር እናያይዛለን። ለምሳሌ፣ የሚመጡ እሽጎችን ለማስኬድ ወይም ከአንዳንዶቹ ጋር ለማገናኘት በኔትወርክ በይነገጽ ላይ ልናስቀምጠው እንችላለን tracepoint
በዋና ውስጥ. በዚህ ጊዜ የማጣቀሻ ቆጣሪው እንዲሁ በአንድ ይጨምራል እና የፋይል ገላጭውን በጫኝ ፕሮግራም ውስጥ መዝጋት እንችላለን.
አሁን ቡት ጫኚውን ብንዘጋው ምን ይሆናል? እንደ የዝግጅት ጀነሬተር (መንጠቆ) አይነት ይወሰናል. ሁሉም የኔትወርክ መንጠቆዎች መጫኛው ከተጠናቀቀ በኋላ ይኖራሉ, እነዚህ ዓለም አቀፍ መንጠቆዎች የሚባሉት ናቸው. እና ለምሳሌ ፣ የመከታተያ ፕሮግራሞች የፈጠሩት ሂደት ካለቀ በኋላ ይለቀቃሉ (እና ስለዚህ “አካባቢያዊ ፣ ከ “አካባቢያዊ ወደ ሂደቱ” ይባላሉ)። በቴክኒካዊ ሁኔታ, የአካባቢ መንጠቆዎች ሁልጊዜ በተጠቃሚ ቦታ ውስጥ ተጓዳኝ የፋይል ገላጭ አላቸው እና ስለዚህ ሂደቱ ሲዘጋ ይዘጋሉ, ነገር ግን ዓለም አቀፋዊ መንጠቆዎች አያደርጉም. በሚከተለው ስእል ውስጥ, ቀይ መስቀሎች በመጠቀም, እኔ ጫኚ ፕሮግራም መቋረጥ የአካባቢ እና ዓለም አቀፍ መንጠቆ ውስጥ ነገሮች ሕይወት ላይ ተጽዕኖ እንዴት ለማሳየት እሞክራለሁ.
በአካባቢያዊ እና በአለምአቀፍ መንጠቆዎች መካከል ልዩነት ለምን አለ? አንዳንድ የአውታረ መረብ ፕሮግራሞችን ማስኬድ ያለ የተጠቃሚ ቦታ ትርጉም ይሰጣል ፣ ለምሳሌ ፣ የ DDoS ጥበቃን ያስቡ - ቡት ጫኚው ደንቦቹን ይጽፋል እና የ BPF ፕሮግራሙን ከአውታረ መረብ በይነገጽ ጋር ያገናኛል ፣ ከዚያ በኋላ ቡት ጫኚው ሄዶ እራሱን ሊያጠፋ ይችላል። በሌላ በኩል በአስር ደቂቃዎች ውስጥ በጉልበቶችዎ ላይ የፃፉትን የማረም የመከታተያ ፕሮግራም ያስቡ - ሲጠናቀቅ በስርዓቱ ውስጥ ምንም ቆሻሻ እንዳይኖር ይፈልጋሉ እና የአካባቢ መንጠቆዎች ያንን ያረጋግጣሉ።
በሌላ በኩል፣ በከርነል ውስጥ ካለው የመከታተያ ነጥብ ጋር መገናኘት እና ለብዙ አመታት ስታቲስቲክስን መሰብሰብ እንደምትፈልግ አስብ። በዚህ አጋጣሚ የተጠቃሚውን ክፍል ማጠናቀቅ እና ከጊዜ ወደ ጊዜ ወደ ስታቲስቲክስ መመለስ ይፈልጋሉ. የ bpf ፋይል ስርዓት ይህንን እድል ይሰጣል. BPF ነገሮችን የሚያጣቅሱ ፋይሎች እንዲፈጠሩ እና በዚህም እንዲጨምሩ የሚያስችል በማህደረ ትውስታ ውስጥ ብቻ የሚገኝ የውሸት ፋይል ስርዓት ነው። refcount
እቃዎች. ከዚህ በኋላ ጫኚው መውጣት ይችላል, እና የፈጠራቸው ነገሮች በህይወት ይቆያሉ.
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 ፕሮግራሞች ፕሮግራሙን በበረራ ላይ ለመተካት ያስችሉዎታል, ማለትም. ቅደም ተከተል atomity ያቅርቡ replace = detach old program, attach new program
. በዚህ ሁኔታ ፣ ሁሉም የድሮው የፕሮግራሙ ስሪት ንቁ ሁኔታዎች ስራቸውን ያጠናቅቃሉ ፣ እና አዲስ የክስተት ተቆጣጣሪዎች ከአዲሱ ፕሮግራም ይፈጠራሉ ፣ እና እዚህ “አቶሚቲ” ማለት አንድም ክስተት አያመልጥም።
ፕሮግራሞችን ከክስተት ምንጮች ጋር በማያያዝ ላይ
በዚህ ጽሑፍ ውስጥ ፕሮግራሞችን ከዝግጅቱ ምንጮች ጋር ማገናኘት በተናጥል አንገልጽም ፣ ምክንያቱም ይህንን በአንድ የተወሰነ የፕሮግራም አውድ ውስጥ ማጥናት ምክንያታዊ ነው። ሴ.ሜ.
የbpf ስርዓት ጥሪን በመጠቀም ነገሮችን ማቀናበር
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 መመሪያዎችን ስብስብ ወስዶ ወደ ከርነል ይጭናል. በሚጫኑበት ጊዜ አረጋጋጩ ተጀምሯል, እና ከዚያ የጂአይቲ ማጠናከሪያ እና, ከተሳካ አፈፃፀም በኋላ, የፕሮግራሙ ፋይል ገላጭ ወደ ተጠቃሚው ይመለሳል. ቀጥሎ ምን እንደሚገጥመው ባለፈው ክፍል አይተናል
አሁን ቀላል 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
16 ባይት ይወስዳል - ሁለት መመሪያዎች - በ BPF አርክቴክቸር ውስጥ የሁለትዮሽ ኮዶች ፣ ግን በአፍ መፍቻው ቅርፅ (x86_64) ቀድሞውኑ 40 ባይት ነው። ፕሮግራማችንን በዋናው መልክ እንመልከተው፡-
# bpftool prog dump xlated id 390
0: (b7) r0 = 2
1: (95) exit
ምንም አያስደንቅም. አሁን በጂአይቲ ኮምፕሌተር የተፈጠረውን ኮድ እንመልከት፡-
# 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)
ነገር ግን በፍትሃዊነት፣ ፕሮግራማችን በጣም ቀላል ነው፣ እና ቀላል ላልሆኑ ፕሮግራሞች በጂአይቲ ኮምፕሌተር የተጨመረው ፕሮሎግ እና አፈ ታሪክ በእርግጥ ያስፈልጋል።
ካርታዎች
BPF ፕሮግራሞች ለሌሎች BPF ፕሮግራሞች እና በተጠቃሚ ቦታ ላሉ ፕሮግራሞች ተደራሽ የሆኑ የተዋቀሩ የማህደረ ትውስታ ቦታዎችን መጠቀም ይችላሉ። እነዚህ ነገሮች ካርታ ይባላሉ እና በዚህ ክፍል የስርዓት ጥሪን በመጠቀም እንዴት እንደሚጠቀሙባቸው እናሳያለን። bpf
.
ወዲያውኑ የካርታዎች ችሎታዎች የጋራ ማህደረ ትውስታን ለመድረስ ብቻ የተገደቡ አይደሉም እንበል። ልዩ ዓላማ ያላቸው ካርታዎች ለምሳሌ የ BPF ፕሮግራሞች ጠቋሚዎች ወይም የአውታረ መረብ መገናኛዎች ጠቋሚዎች, ከፐርፍ ዝግጅቶች ጋር ለመስራት ካርታዎች, ወዘተ. አንባቢን እንዳናደናግር ስለእነሱ እዚህ አንነጋገርም። ከዚህ ውጪ፣ ይህ ለምሳሌዎቻችን አስፈላጊ ስላልሆነ የማመሳሰል ጉዳዮችን ችላ እንላለን። የሚገኙ የካርታ ዓይነቶች ሙሉ ዝርዝር በ ውስጥ ይገኛሉ <linux/bpf.h>
BPF_MAP_TYPE_HASH
.
የሃሽ ጠረጴዛ ከፈጠሩ፣ C++ ይበሉ፣ ይላሉ unordered_map<int,long> woo
, በሩሲያኛ ትርጉሙ "ጠረጴዛ እፈልጋለሁ woo
ያልተገደበ መጠን ፣ ቁልፎቹ ዓይነት ናቸው። int
, እና እሴቶቹ ዓይነት ናቸው long
" የቢፒኤፍ ሃሽ ሠንጠረዥ ለመፍጠር የሠንጠረዡን ከፍተኛ መጠን ከመግለጽ በስተቀር ብዙ ተመሳሳይ ነገር ማድረግ አለብን, እና የቁልፍ እና የእሴቶችን አይነት ከመጥቀስ ይልቅ መጠኖቻቸውን በባይት መግለጽ አለብን. . ካርታዎችን ለመፍጠር ትዕዛዙን ይጠቀሙ 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
, እኛ የምንለው ውስጥ "እኔ ቁልፎች እና መጠን እሴቶች ጋር hash ጠረጴዛ ያስፈልጋቸዋል 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 ነው (አንድ ጊዜ ያጠናቅራል ፣ በሁሉም ቦታ ያሂዱ) - ከአንድ ከርነል ወደ ሌላ ተንቀሳቃሽ የ BPF ፕሮግራሞችን ለመፃፍ የሚያስችልዎ ፣ በተለያዩ ኤፒአይዎች ላይ የመሄድ ችሎታ ያለው (ለምሳሌ ፣ የከርነል መዋቅር ከስሪት ሲቀየር) ወደ ስሪት)። ከ CO-RE ጋር ለመስራት፣ የእርስዎ ከርነል ከ 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]
. ነገር ግን ከከርነል ውጭ ለሚኖሩ አፕሊኬሽኖች ፍላጎቶች የተለየ ማከማቻ ተይዟል።
በዚህ ክፍል ውስጥ ጥቅም ላይ የሚውል ፕሮጀክት እንዴት መፍጠር እንደሚችሉ እንመለከታለን libbpf
፣ በርካታ (ብዙ ወይም ያነሰ ትርጉም የለሽ) የሙከራ ፕሮግራሞችን እንፃፍ እና ሁሉም እንዴት እንደሚሰራ በዝርዝር እንመርምር። ይህ በሚቀጥሉት ክፍሎች የ 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
ሁለት አማራጮችን ይሰጠናል - ዝቅተኛ ደረጃ ኤፒአይ ወይም ከፍተኛ ደረጃ ኤፒአይ ይጠቀሙ። ለቀጣይ ጥናታቸው በትንሹ ጥረት ቢፒኤፍ ፕሮግራሞችን እንዴት መጻፍ፣ መጫን እና ማገናኘት ስለምንፈልግ በሁለተኛው መንገድ እንሄዳለን።
በመጀመሪያ, ተመሳሳይ መገልገያ በመጠቀም የፕሮግራማችንን "አጽም" ከሱ ሁለትዮሽ ማመንጨት አለብን bpftool
- የ BPF ዓለም የስዊስ ቢላዋ (በጥሬው ሊወሰድ ይችላል ፣ ከ BPF ፈጣሪዎች እና ጠባቂዎች አንዱ የሆነው ዳንኤል ቦርክማን ስዊዘርላንድ ነው)
$ bpftool gen skeleton xdp-simple.bpf.o > xdp-simple.skel.h
በፋይል ውስጥ xdp-simple.skel.h
የፕሮግራማችንን ሁለትዮሽ ኮድ እና የማስተዳደር ተግባራትን ይይዛል - እቃችንን መጫን ፣ ማያያዝ ፣ መሰረዝ። በቀላል ጉዳያችን ይህ ከመጠን በላይ መጨናነቅን ይመስላል ነገር ግን የነገር ፋይሉ ብዙ BPF ፕሮግራሞችን እና ካርታዎችን በሚይዝበት ሁኔታ ላይም ይሠራል እና ይህንን ግዙፍ ኢኤልኤፍ ለመጫን አፅሙን ማመንጨት እና አንድ ወይም ሁለት ተግባራትን ከብጁ መተግበሪያ መደወል አለብን። እየጻፉ ነው አሁን እንቀጥል።
በትክክል አነጋገር፣ የእኛ ሎደር ፕሮግራማችን ቀላል ነው፡-
#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;
};
የዝቅተኛ ደረጃ ኤፒአይ ምልክቶችን እዚህ ማየት እንችላለን፡ መዋቅሩ 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
አዲስ ነገር! ፕሮግራሙ የC ምንጭ ፋይላችንን ቁርጥራጮች አሳትሟል።ይህ የተደረገው በቤተ-መጽሐፍት ነው። libbpf
, በሁለትዮሽ ውስጥ የማረም ክፍሉን ያገኘው, ወደ BTF ነገር ያጠናቀረው, በመጠቀም ወደ ከርነል ጭኖታል. BPF_BTF_LOAD
, እና ከዚያ ፕሮግራሙን በትእዛዙ ሲጫኑ የተገኘውን የፋይል ገላጭ ገልፀዋል BPG_PROG_LOAD
.
የከርነል አጋዥዎች
BPF ፕሮግራሞች "ውጫዊ" ተግባራትን - የከርነል አጋዥዎችን ማሄድ ይችላሉ. እነዚህ አጋዥ ተግባራት BPF ፕሮግራሞች የከርነል አወቃቀሮችን እንዲደርሱ፣ ካርታዎችን እንዲያስተዳድሩ እና እንዲሁም ከ“እውነተኛው ዓለም” ጋር እንዲገናኙ ያስችላቸዋል - የፐርፍ ዝግጅቶችን መፍጠር፣ ሃርድዌርን መቆጣጠር (ለምሳሌ ፓኬቶችን አቅጣጫ ማዞር) ወዘተ።
ምሳሌ፡ 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
. በጥቅሶች የተገለፀው አመክንዮአዊ ፍቺ ስለሆነ እና በ C ቋንቋ አነጋገር የአንድ ሙሉ የኮንክሪት መዋቅር ፍቺ በሌሎች ቦታዎች ይከሰታል። በተለይም በፋይሉ ውስጥ 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
};
ያም ማለት ለእያንዳንዱ የቢፒኤፍ ፕሮግራም አይነት የመረጃ አወቃቀሩ ጠቋሚ ይገለጻል። 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
- ዜሮ. አረጋጋጭ በተጠቀመው የ ABI ስምምነት መሰረት፣ ይህ የረዳት ተግባር ቁጥር ስምንት ጥሪ ነው። አንዴ ከተጀመረ, አመክንዮው ቀላል ነው. ከመመዝገቢያ ዋጋ ይመለሱ r0
ተገልብጧል r1
እና በመስመሮች 2,3 ላይ ወደ መተየብ ይቀየራል u32
- የላይኛው 32 ቢት ተጠርጓል. በመስመሮች 4,5,6,7፣2፣XNUMX፣XNUMX XNUMX እንመለሳለን (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;
}
ፕሮግራማችን የሚሰራበትን ሲፒዩ ቁጥር ያትማል። እናጠናቅቅና ኮዱን እንይ፡
$ 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
በድርድር ውስጥ ለሚገኘው ተዛማጅ ግቤት ጠቋሚ እናገኛለን ፣ ይህም በአንድ እንጨምራለን ። ወደ ራሽያኛ ተተርጉሟል፡ ሲፒዩ የገቢ ፓኬቶችን ያከናወነባቸውን ስታቲስቲክስ እናሰላለን። ፕሮግራሙን ለማስኬድ እንሞክር-
$ 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
, እና የመጀመሪያው አይኤምኤም ወደ ካርታችን ፋይል ገላጭ እና, እኩል ከሆነ, ለምሳሌ, 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
አመክንዮውን ለመከተል ቀላል ለማድረግ, ለእነዚህ አላማዎች ምሳሌያችንን እንደገና እንጽፋለን 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ን የፈጠሩት ሰዎች ከኦንላይን ሊኑክስ ማህበረሰብ ናቸው፣ ይህ ማለት ለእነሱ በጣም የተለመዱትን ተጠቅመዋል ማለት ነው (ነገር ግን ለ የተለመደ ሰዎች) ከከርነል ጋር ለመግባባት በይነገጽ: xdp_attach
ኮድ ከ እየቀዳ ነው። libbpf
, ማለትም ከፋይሉ netlink.c
እንኳን ወደ የnetlink ሶኬቶች አለም በደህና መጡ
የnetlink ሶኬት አይነት ይክፈቱ 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
, ከጥቅሉ ሊቀርብ የሚችል. ሆኖም ግን, BPF በመገንባት ላይ ስለሆነ, ከርነል እና መሳሪያዎች በየጊዜው እየተለዋወጡ ነው, ከ 2019 ጀምሮ የቆዩ ዘዴዎችን በመጠቀም የ BPF ፕሮግራሞችን መጻፍ ካልፈለጉ, ከዚያም ማጠናቀር ይኖርብዎታል.
llvm
/clang
pahole
- አንኳርነቱ
bpftool
(ለማጣቀሻ፣ ይህ ክፍል እና በአንቀጹ ውስጥ ያሉት ሁሉም ምሳሌዎች በዴቢያን 10 ላይ ተካሂደዋል።)
lvm/clang
BPF ከኤልኤልቪኤም ጋር ወዳጃዊ ነው እና ምንም እንኳን በቅርብ ጊዜ የ BPF ፕሮግራሞች gccን በመጠቀም ሊጣመሩ ቢችሉም ሁሉም ወቅታዊ ልማት ለኤልኤልቪኤም ነው የሚከናወነው። ስለዚህ, በመጀመሪያ, የአሁኑን ስሪት እንገነባለን clang
ከጂት፡
$ 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
በእኔ የተወሰደ
አሁን የገነባናቸው ፕሮግራሞችን አንጭናቸውም፣ ይልቁንስ እንጨምራቸው PATH
, ለምሳሌ:
export PATH="`pwd`/bin:$PATH"
(ይህ ሊጨመር ይችላል .bashrc
ወይም ወደተለየ ፋይል። በግሌ እንደዚህ አይነት ነገሮችን እጨምራለሁ ~/bin/activate-llvm.sh
እና አስፈላጊ ሆኖ ሲገኝ አደርገዋለሁ . activate-llvm.sh
.)
Pahole እና 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-next
*-next
ከተዘረዘሩት ውስጥ አስኳሎች በጣም ያልተረጋጉ ናቸው)።
የከርነል ውቅር ፋይሎችን እንዴት ማስተዳደር እንደሚቻል ለመነጋገር ከዚህ ጽሑፍ ወሰን በላይ ነው - ይህንን እንዴት ማድረግ እንዳለብዎ አስቀድመው ያውቃሉ ፣ ወይም
ከላይ ካሉት አስኳሎች አንዱን አውርድ፡-
$ 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 መሠረተ ልማት እድገታቸው ገና ብዙ ቢሆኑም ስርዓቱ አስተማማኝ እና ከሁሉም በላይ ውጤታማ የንግድ ሥራ አመክንዮ እንዲገነቡ የሚያስችልዎ የተረጋጋ ABI አለው።
በእኔ አስተያየት ቴክኖሎጂው በጣም ተወዳጅ እየሆነ መጥቷል ምክንያቱም በአንድ በኩል ጥቅም ላይ ሊውል እንደሚችል ልብ ማለት እፈልጋለሁ. ተጫወት (የማሽን አርክቴክቸር በአንድ ምሽት ብዙ ወይም ያነሰ ሊረዳ ይችላል) እና በሌላ በኩል ደግሞ ከመታየቱ በፊት ሊፈቱ ያልቻሉ ችግሮችን ለመፍታት (በሚያምር)። እነዚህ ሁለት አካላት አንድ ላይ ሰዎች እንዲሞክሩ እና እንዲያልሙ ያስገድዷቸዋል, ይህም ብዙ እና ብዙ አዳዲስ መፍትሄዎች እንዲፈጠሩ ያደርጋል.
ይህ ጽሑፍ ምንም እንኳን በተለይ አጭር ባይሆንም ለ BPF ዓለም መግቢያ ብቻ ነው እና "የላቁ" ባህሪያትን እና የሕንፃውን አስፈላጊ ክፍሎች አይገልጽም. ወደፊት የሚሄደው እቅድ ይህን ይመስላል፡ የሚቀጥለው መጣጥፍ የ BPF ፕሮግራም አይነቶች አጠቃላይ እይታ ይሆናል (በ 5.8 ከርነል ውስጥ የሚደገፉ 30 የፕሮግራም አይነቶች አሉ) በመጨረሻ የከርነል መፈለጊያ ፕሮግራሞችን በመጠቀም እውነተኛ የ BPF አፕሊኬሽኖችን እንዴት መፃፍ እንደምንችል እንመለከታለን። እንደ ምሳሌ፣ ከዚያም የBPF አውታረ መረብ እና የደህንነት አፕሊኬሽኖች ምሳሌዎችን በመከተል ስለ BPF architecture ለበለጠ ጥልቀት ኮርስ ጊዜው አሁን ነው።
በዚህ ተከታታይ ውስጥ ያለፉ መጣጥፎች
አገናኞች
-
BPF እና XDP የማጣቀሻ መመሪያ - በ BPF ላይ ከሲሊየም ወይም በትክክል ከ BPF ፈጣሪዎች እና ጠባቂዎች አንዱ የሆነው ዳንኤል ቦርክማን የተገኘ ሰነድ። ይህ ከመጀመሪያዎቹ ከባድ መግለጫዎች አንዱ ነው, እሱም ከሌሎቹ የሚለየው ዳንኤል ስለ ጽፏል በትክክል ስለሚያውቅ እና እዚያ ምንም ስህተቶች የሉም. በተለይም ይህ ሰነድ ታዋቂውን መገልገያ በመጠቀም ከ XDP እና TC ዓይነቶች ከ BPF ፕሮግራሞች ጋር እንዴት እንደሚሰራ ይገልጻልip
ከጥቅሉiproute2
. -
Documentation/networking/filter.txt - ኦሪጅናል ፋይል ለጥንታዊ እና ከዚያም የተራዘመ BPF። ወደ መሰብሰቢያ ቋንቋ እና ቴክኒካል አርክቴክቸር ዝርዝሮች በጥልቀት ለመፈተሽ ከፈለጉ ጥሩ ንባብ። -
ስለ BPF ከፌስቡክ ብሎግ . በጣም አልፎ አልፎ ነው የሚዘመነው፣ ግን በትክክል፣ አሌክሲ ስታሮቮይቶቭ (የ eBPF ደራሲ) እና አንድሪ ናክሪይኮ - (ጠባቂ) እዚያ እንደሚጽፉ።libbpf
). -
የ bpftool ምስጢሮች . ከ Quentin Monnet የ bpftool አጠቃቀም ምሳሌዎች እና ሚስጥሮች ያለው አዝናኝ የትዊተር ክር። -
ወደ BPF ይዝለሉ፡ የንባብ ቁሳቁስ ዝርዝር . ከኩዌንቲን ሞኔት ወደ BPF ሰነድ የሚወስዱ ግዙፍ (እና አሁንም የሚቆይ) አገናኞች ዝርዝር።
ምንጭ: hab.com