ನಾವು XDP ಮೇಲೆ DDoS ದಾಳಿಯ ವಿರುದ್ಧ ರಕ್ಷಣೆಯನ್ನು ಬರೆಯುತ್ತೇವೆ. ಪರಮಾಣು ಭಾಗ

eXpress Data Path (XDP) ತಂತ್ರಜ್ಞಾನವು ಪ್ಯಾಕೆಟ್‌ಗಳು ಕರ್ನಲ್ ನೆಟ್‌ವರ್ಕ್ ಸ್ಟಾಕ್‌ಗೆ ಪ್ರವೇಶಿಸುವ ಮೊದಲು ಲಿನಕ್ಸ್ ಇಂಟರ್‌ಫೇಸ್‌ಗಳಲ್ಲಿ ಯಾದೃಚ್ಛಿಕ ಟ್ರಾಫಿಕ್ ಪ್ರೊಸೆಸಿಂಗ್ ಅನ್ನು ನಿರ್ವಹಿಸಲು ಅನುಮತಿಸುತ್ತದೆ. XDP ಯ ಅಪ್ಲಿಕೇಶನ್ - DDoS ದಾಳಿಗಳ ವಿರುದ್ಧ ರಕ್ಷಣೆ (CloudFlare), ಸಂಕೀರ್ಣ ಫಿಲ್ಟರ್‌ಗಳು, ಅಂಕಿಅಂಶಗಳ ಸಂಗ್ರಹ (ನೆಟ್‌ಫ್ಲಿಕ್ಸ್). XDP ಪ್ರೋಗ್ರಾಂಗಳನ್ನು eBPF ವರ್ಚುವಲ್ ಗಣಕದಿಂದ ಕಾರ್ಯಗತಗೊಳಿಸಲಾಗುತ್ತದೆ, ಆದ್ದರಿಂದ ಅವುಗಳು ತಮ್ಮ ಕೋಡ್ ಮತ್ತು ಫಿಲ್ಟರ್ ಪ್ರಕಾರವನ್ನು ಅವಲಂಬಿಸಿ ಲಭ್ಯವಿರುವ ಕರ್ನಲ್ ಕಾರ್ಯಗಳ ಮೇಲೆ ನಿರ್ಬಂಧಗಳನ್ನು ಹೊಂದಿವೆ.

ಲೇಖನವು XDP ಯಲ್ಲಿನ ಹಲವಾರು ವಸ್ತುಗಳ ನ್ಯೂನತೆಗಳನ್ನು ತುಂಬಲು ಉದ್ದೇಶಿಸಿದೆ. ಮೊದಲನೆಯದಾಗಿ, ಅವರು XDP ಯ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ತಕ್ಷಣವೇ ಬೈಪಾಸ್ ಮಾಡುವ ರೆಡಿಮೇಡ್ ಕೋಡ್ ಅನ್ನು ಒದಗಿಸುತ್ತಾರೆ: ಇದು ಪರಿಶೀಲನೆಗಾಗಿ ಸಿದ್ಧವಾಗಿದೆ ಅಥವಾ ಸಮಸ್ಯೆಗಳನ್ನು ಉಂಟುಮಾಡಲು ತುಂಬಾ ಸರಳವಾಗಿದೆ. ನಂತರ ನೀವು ಮೊದಲಿನಿಂದ ನಿಮ್ಮ ಕೋಡ್ ಅನ್ನು ಬರೆಯಲು ಪ್ರಯತ್ನಿಸಿದಾಗ, ವಿಶಿಷ್ಟ ದೋಷಗಳೊಂದಿಗೆ ಏನು ಮಾಡಬೇಕೆಂದು ನಿಮಗೆ ತಿಳಿದಿರುವುದಿಲ್ಲ. ಎರಡನೆಯದಾಗಿ, VM ಮತ್ತು ಹಾರ್ಡ್‌ವೇರ್ ಇಲ್ಲದೆಯೇ XDP ಅನ್ನು ಸ್ಥಳೀಯವಾಗಿ ಪರೀಕ್ಷಿಸುವ ವಿಧಾನಗಳು ಅವುಗಳು ತಮ್ಮದೇ ಆದ ಅಪಾಯಗಳನ್ನು ಹೊಂದಿದ್ದರೂ ಸಹ ಒಳಗೊಂಡಿರುವುದಿಲ್ಲ. ಪಠ್ಯವು XDP ಮತ್ತು eBPF ನಲ್ಲಿ ಆಸಕ್ತಿ ಹೊಂದಿರುವ ನೆಟ್‌ವರ್ಕಿಂಗ್ ಮತ್ತು ಲಿನಕ್ಸ್‌ನೊಂದಿಗೆ ಪರಿಚಿತವಾಗಿರುವ ಪ್ರೋಗ್ರಾಮರ್‌ಗಳಿಗಾಗಿ ಉದ್ದೇಶಿಸಲಾಗಿದೆ.

ಈ ಭಾಗದಲ್ಲಿ, XDP ಫಿಲ್ಟರ್ ಅನ್ನು ಹೇಗೆ ಜೋಡಿಸಲಾಗಿದೆ ಮತ್ತು ಅದನ್ನು ಹೇಗೆ ಪರೀಕ್ಷಿಸಬೇಕು ಎಂಬುದನ್ನು ನಾವು ವಿವರವಾಗಿ ಅರ್ಥಮಾಡಿಕೊಳ್ಳುತ್ತೇವೆ, ನಂತರ ನಾವು ಪ್ಯಾಕೆಟ್ ಸಂಸ್ಕರಣಾ ಮಟ್ಟದಲ್ಲಿ ಪ್ರಸಿದ್ಧ SYN ಕುಕೀಸ್ ಕಾರ್ಯವಿಧಾನದ ಸರಳ ಆವೃತ್ತಿಯನ್ನು ಬರೆಯುತ್ತೇವೆ. ನಾವು ಇನ್ನೂ "ಬಿಳಿ ಪಟ್ಟಿ" ಅನ್ನು ರಚಿಸುವುದಿಲ್ಲ
ಪರಿಶೀಲಿಸಿದ ಕ್ಲೈಂಟ್‌ಗಳು, ಕೌಂಟರ್‌ಗಳನ್ನು ಇರಿಸಿ ಮತ್ತು ಫಿಲ್ಟರ್ ಅನ್ನು ನಿರ್ವಹಿಸಿ - ಸಾಕಷ್ಟು ಲಾಗ್‌ಗಳು.

ನಾವು C ನಲ್ಲಿ ಬರೆಯುತ್ತೇವೆ - ಇದು ಫ್ಯಾಶನ್ ಅಲ್ಲ, ಆದರೆ ಇದು ಪ್ರಾಯೋಗಿಕವಾಗಿದೆ. ಎಲ್ಲಾ ಕೋಡ್ ಕೊನೆಯಲ್ಲಿರುವ ಲಿಂಕ್ ಮೂಲಕ GitHub ನಲ್ಲಿ ಲಭ್ಯವಿದೆ ಮತ್ತು ಲೇಖನದಲ್ಲಿ ವಿವರಿಸಿದ ಹಂತಗಳ ಪ್ರಕಾರ ಕಮಿಟ್‌ಗಳಾಗಿ ವಿಂಗಡಿಸಲಾಗಿದೆ.

ಹಕ್ಕುತ್ಯಾಗ. ಈ ಲೇಖನದ ಅವಧಿಯಲ್ಲಿ, DDoS ದಾಳಿಯನ್ನು ತಡೆಯಲು ನಾನು ಮಿನಿ-ಪರಿಹಾರವನ್ನು ಅಭಿವೃದ್ಧಿಪಡಿಸುತ್ತೇನೆ, ಏಕೆಂದರೆ ಇದು XDP ಮತ್ತು ನನ್ನ ಪರಿಣತಿಯ ಕ್ಷೇತ್ರಕ್ಕೆ ವಾಸ್ತವಿಕ ಕಾರ್ಯವಾಗಿದೆ. ಆದಾಗ್ಯೂ, ತಂತ್ರಜ್ಞಾನವನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳುವುದು ಮುಖ್ಯ ಗುರಿಯಾಗಿದೆ; ಇದು ಸಿದ್ಧ ರಕ್ಷಣೆಯನ್ನು ರಚಿಸಲು ಮಾರ್ಗದರ್ಶಿಯಾಗಿಲ್ಲ. ಟ್ಯುಟೋರಿಯಲ್ ಕೋಡ್ ಅನ್ನು ಆಪ್ಟಿಮೈಸ್ ಮಾಡಲಾಗಿಲ್ಲ ಮತ್ತು ಕೆಲವು ಸೂಕ್ಷ್ಮ ವ್ಯತ್ಯಾಸಗಳನ್ನು ಬಿಟ್ಟುಬಿಡುತ್ತದೆ.

XDP ಸಂಕ್ಷಿಪ್ತ ಅವಲೋಕನ

ದಸ್ತಾವೇಜನ್ನು ಮತ್ತು ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಲೇಖನಗಳನ್ನು ನಕಲು ಮಾಡದಂತೆ ನಾನು ಪ್ರಮುಖ ಅಂಶಗಳನ್ನು ಮಾತ್ರ ವಿವರಿಸುತ್ತೇನೆ.

ಆದ್ದರಿಂದ, ಫಿಲ್ಟರ್ ಕೋಡ್ ಅನ್ನು ಕರ್ನಲ್ಗೆ ಲೋಡ್ ಮಾಡಲಾಗಿದೆ. ಒಳಬರುವ ಪ್ಯಾಕೆಟ್‌ಗಳನ್ನು ಫಿಲ್ಟರ್‌ಗೆ ರವಾನಿಸಲಾಗುತ್ತದೆ. ಪರಿಣಾಮವಾಗಿ, ಫಿಲ್ಟರ್ ನಿರ್ಧಾರ ತೆಗೆದುಕೊಳ್ಳಬೇಕು: ಪ್ಯಾಕೆಟ್ ಅನ್ನು ಕರ್ನಲ್‌ಗೆ ರವಾನಿಸಿ (XDP_PASS), ಡ್ರಾಪ್ ಪ್ಯಾಕೆಟ್ (XDP_DROP) ಅಥವಾ ಅದನ್ನು ಮರಳಿ ಕಳುಹಿಸಿ (XDP_TX) ಫಿಲ್ಟರ್ ಪ್ಯಾಕೇಜ್ ಅನ್ನು ಬದಲಾಯಿಸಬಹುದು, ಇದು ವಿಶೇಷವಾಗಿ ಸತ್ಯವಾಗಿದೆ XDP_TX. ನೀವು ಪ್ರೋಗ್ರಾಂ ಅನ್ನು ಸಹ ಸ್ಥಗಿತಗೊಳಿಸಬಹುದು (XDP_ABORTED) ಮತ್ತು ಪ್ಯಾಕೇಜ್ ಅನ್ನು ಮರುಹೊಂದಿಸಿ, ಆದರೆ ಇದು ಸಾದೃಶ್ಯವಾಗಿದೆ assert(0) - ಡೀಬಗ್ ಮಾಡಲು.

eBPF (ವಿಸ್ತೃತ ಬರ್ಕ್ಲಿ ಪ್ಯಾಕೆಟ್ ಫಿಲ್ಟರ್) ವರ್ಚುವಲ್ ಯಂತ್ರವನ್ನು ಉದ್ದೇಶಪೂರ್ವಕವಾಗಿ ಸರಳಗೊಳಿಸಲಾಗಿದೆ ಇದರಿಂದ ಕೋಡ್ ಲೂಪ್ ಆಗುವುದಿಲ್ಲ ಮತ್ತು ಇತರ ಜನರ ಸ್ಮರಣೆಯನ್ನು ಹಾನಿಗೊಳಿಸುವುದಿಲ್ಲ ಎಂದು ಕರ್ನಲ್ ಪರಿಶೀಲಿಸಬಹುದು. ಸಂಚಿತ ನಿರ್ಬಂಧಗಳು ಮತ್ತು ಪರಿಶೀಲನೆಗಳು:

  • ಕುಣಿಕೆಗಳು (ಹಿಂದಕ್ಕೆ) ನಿಷೇಧಿಸಲಾಗಿದೆ.
  • ಡೇಟಾಗೆ ಸ್ಟಾಕ್ ಇದೆ, ಆದರೆ ಯಾವುದೇ ಕಾರ್ಯಗಳಿಲ್ಲ (ಎಲ್ಲಾ ಸಿ ಕಾರ್ಯಗಳನ್ನು ಇನ್‌ಲೈನ್ ಮಾಡಬೇಕು).
  • ಸ್ಟಾಕ್ ಮತ್ತು ಪ್ಯಾಕೆಟ್ ಬಫರ್ ಹೊರಗೆ ಮೆಮೊರಿ ಪ್ರವೇಶಗಳನ್ನು ನಿಷೇಧಿಸಲಾಗಿದೆ.
  • ಕೋಡ್ ಗಾತ್ರವು ಸೀಮಿತವಾಗಿದೆ, ಆದರೆ ಪ್ರಾಯೋಗಿಕವಾಗಿ ಇದು ತುಂಬಾ ಮಹತ್ವದ್ದಾಗಿಲ್ಲ.
  • ವಿಶೇಷ ಕರ್ನಲ್ ಕಾರ್ಯಗಳಿಗೆ (eBPF ಸಹಾಯಕರು) ಕರೆಗಳನ್ನು ಮಾತ್ರ ಅನುಮತಿಸಲಾಗಿದೆ.

ಫಿಲ್ಟರ್ ಅನ್ನು ವಿನ್ಯಾಸಗೊಳಿಸುವುದು ಮತ್ತು ಸ್ಥಾಪಿಸುವುದು ಈ ರೀತಿ ಕಾಣುತ್ತದೆ:

  1. ಮೂಲ ಕೋಡ್ (ಉದಾ kernel.c) ವಸ್ತುವಾಗಿ ಸಂಕಲಿಸಲಾಗಿದೆ (kernel.oeBPF ವರ್ಚುವಲ್ ಮೆಷಿನ್ ಆರ್ಕಿಟೆಕ್ಚರ್‌ಗಾಗಿ. ಅಕ್ಟೋಬರ್ 2019 ರ ಹೊತ್ತಿಗೆ, eBPF ಗೆ ಸಂಕಲನವನ್ನು ಕ್ಲಾಂಗ್ ಬೆಂಬಲಿಸುತ್ತದೆ ಮತ್ತು GCC 10.1 ರಲ್ಲಿ ಭರವಸೆ ನೀಡಿದೆ.
  2. ಈ ಆಬ್ಜೆಕ್ಟ್ ಕೋಡ್ ಕರ್ನಲ್ ರಚನೆಗಳಿಗೆ ಕರೆಗಳನ್ನು ಹೊಂದಿದ್ದರೆ (ಉದಾಹರಣೆಗೆ, ಕೋಷ್ಟಕಗಳು ಮತ್ತು ಕೌಂಟರ್‌ಗಳು), ಅವುಗಳ ID ಗಳನ್ನು ಸೊನ್ನೆಗಳಿಂದ ಬದಲಾಯಿಸಲಾಗುತ್ತದೆ, ಅಂದರೆ ಅಂತಹ ಕೋಡ್ ಅನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸಲಾಗುವುದಿಲ್ಲ. ಕರ್ನಲ್‌ಗೆ ಲೋಡ್ ಮಾಡುವ ಮೊದಲು, ನೀವು ಈ ಸೊನ್ನೆಗಳನ್ನು ಕರ್ನಲ್ ಕರೆಗಳ ಮೂಲಕ ರಚಿಸಲಾದ ನಿರ್ದಿಷ್ಟ ವಸ್ತುಗಳ ID ಗಳೊಂದಿಗೆ ಬದಲಾಯಿಸಬೇಕಾಗುತ್ತದೆ (ಕೋಡ್ ಅನ್ನು ಲಿಂಕ್ ಮಾಡಿ). ಬಾಹ್ಯ ಉಪಯುಕ್ತತೆಗಳೊಂದಿಗೆ ನೀವು ಇದನ್ನು ಮಾಡಬಹುದು ಅಥವಾ ನಿರ್ದಿಷ್ಟ ಫಿಲ್ಟರ್ ಅನ್ನು ಲಿಂಕ್ ಮಾಡುವ ಮತ್ತು ಲೋಡ್ ಮಾಡುವ ಪ್ರೋಗ್ರಾಂ ಅನ್ನು ನೀವು ಬರೆಯಬಹುದು.
  3. ಕರ್ನಲ್ ಲೋಡ್ ಮಾಡಲಾದ ಪ್ರೋಗ್ರಾಂ ಅನ್ನು ಪರಿಶೀಲಿಸುತ್ತದೆ. ಚಕ್ರಗಳ ಅನುಪಸ್ಥಿತಿ ಮತ್ತು ಪ್ಯಾಕೆಟ್ ಮತ್ತು ಸ್ಟಾಕ್ ಗಡಿಗಳನ್ನು ಮೀರುವಲ್ಲಿ ವಿಫಲತೆಯನ್ನು ಪರಿಶೀಲಿಸಲಾಗುತ್ತದೆ. ಕೋಡ್ ಸರಿಯಾಗಿದೆ ಎಂದು ಪರಿಶೀಲಕನು ಸಾಬೀತುಪಡಿಸಲು ಸಾಧ್ಯವಾಗದಿದ್ದರೆ, ಪ್ರೋಗ್ರಾಂ ಅನ್ನು ತಿರಸ್ಕರಿಸಲಾಗುತ್ತದೆ - ನೀವು ಅವನನ್ನು ಮೆಚ್ಚಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ.
  4. ಯಶಸ್ವಿ ಪರಿಶೀಲನೆಯ ನಂತರ, ಕರ್ನಲ್ eBPF ಆರ್ಕಿಟೆಕ್ಚರ್ ಆಬ್ಜೆಕ್ಟ್ ಕೋಡ್ ಅನ್ನು ಸಿಸ್ಟಮ್ ಆರ್ಕಿಟೆಕ್ಚರ್‌ಗಾಗಿ (ಸಮಯದಲ್ಲಿ) ಯಂತ್ರ ಕೋಡ್‌ಗೆ ಕಂಪೈಲ್ ಮಾಡುತ್ತದೆ.
  5. ಪ್ರೋಗ್ರಾಂ ಇಂಟರ್ಫೇಸ್ಗೆ ಲಗತ್ತಿಸುತ್ತದೆ ಮತ್ತು ಪ್ಯಾಕೆಟ್ಗಳನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲು ಪ್ರಾರಂಭಿಸುತ್ತದೆ.

XDP ಕರ್ನಲ್‌ನಲ್ಲಿ ಚಲಿಸುವುದರಿಂದ, ಟ್ರೇಸ್ ಲಾಗ್‌ಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಡೀಬಗ್ ಮಾಡುವಿಕೆಯನ್ನು ಕೈಗೊಳ್ಳಲಾಗುತ್ತದೆ ಮತ್ತು ವಾಸ್ತವವಾಗಿ, ಪ್ರೋಗ್ರಾಂ ಫಿಲ್ಟರ್ ಮಾಡುವ ಅಥವಾ ಉತ್ಪಾದಿಸುವ ಪ್ಯಾಕೆಟ್‌ಗಳನ್ನು ಬಳಸುತ್ತದೆ. ಆದಾಗ್ಯೂ, ಡೌನ್‌ಲೋಡ್ ಮಾಡಿದ ಕೋಡ್ ಸಿಸ್ಟಮ್‌ಗೆ ಸುರಕ್ಷಿತವಾಗಿದೆ ಎಂದು eBPF ಖಚಿತಪಡಿಸುತ್ತದೆ, ಆದ್ದರಿಂದ ನೀವು ನಿಮ್ಮ ಸ್ಥಳೀಯ ಲಿನಕ್ಸ್‌ನಲ್ಲಿ ನೇರವಾಗಿ XDP ಯೊಂದಿಗೆ ಪ್ರಯೋಗಿಸಬಹುದು.

ಪರಿಸರವನ್ನು ಸಿದ್ಧಪಡಿಸುವುದು

ಅಸೆಂಬ್ಲಿ

ಕ್ಲಾಂಗ್ ನೇರವಾಗಿ eBPF ಆರ್ಕಿಟೆಕ್ಚರ್‌ಗಾಗಿ ಆಬ್ಜೆಕ್ಟ್ ಕೋಡ್ ಅನ್ನು ಉತ್ಪಾದಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ, ಆದ್ದರಿಂದ ಪ್ರಕ್ರಿಯೆಯು ಎರಡು ಹಂತಗಳನ್ನು ಒಳಗೊಂಡಿದೆ:

  1. ಸಿ ಕೋಡ್ ಅನ್ನು LLVM ಬೈಟ್‌ಕೋಡ್‌ಗೆ ಕಂಪೈಲ್ ಮಾಡಿ (clang -emit-llvm).
  2. ಬೈಟ್‌ಕೋಡ್ ಅನ್ನು eBPF ಆಬ್ಜೆಕ್ಟ್ ಕೋಡ್‌ಗೆ ಪರಿವರ್ತಿಸಿ (llc -march=bpf -filetype=obj).

ಫಿಲ್ಟರ್ ಅನ್ನು ಬರೆಯುವಾಗ, ಸಹಾಯಕ ಕಾರ್ಯಗಳು ಮತ್ತು ಮ್ಯಾಕ್ರೋಗಳೊಂದಿಗೆ ಒಂದೆರಡು ಫೈಲ್ಗಳು ಉಪಯುಕ್ತವಾಗುತ್ತವೆ ಕರ್ನಲ್ ಪರೀಕ್ಷೆಗಳಿಂದ. ಅವು ಕರ್ನಲ್ ಆವೃತ್ತಿಗೆ ಹೊಂದಿಕೆಯಾಗುವುದು ಮುಖ್ಯ (KVER) ಅವುಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ helpers/:

export KVER=v5.3.7
export BASE=https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/tools/testing/selftests/bpf
wget -P helpers --content-disposition "${BASE}/bpf_helpers.h?h=${KVER}" "${BASE}/bpf_endian.h?h=${KVER}"
unset KVER BASE

ಆರ್ಚ್ ಲಿನಕ್ಸ್‌ಗಾಗಿ ಮೇಕ್‌ಫೈಲ್ (ಕರ್ನಲ್ 5.3.7):

CLANG ?= clang
LLC ?= llc

KDIR ?= /lib/modules/$(shell uname -r)/build
ARCH ?= $(subst x86_64,x86,$(shell uname -m))

CFLAGS = 
    -Ihelpers 
    
    -I$(KDIR)/include 
    -I$(KDIR)/include/uapi 
    -I$(KDIR)/include/generated/uapi 
    -I$(KDIR)/arch/$(ARCH)/include 
    -I$(KDIR)/arch/$(ARCH)/include/generated 
    -I$(KDIR)/arch/$(ARCH)/include/uapi 
    -I$(KDIR)/arch/$(ARCH)/include/generated/uapi 
    -D__KERNEL__ 
    
    -fno-stack-protector -O2 -g

xdp_%.o: xdp_%.c Makefile
    $(CLANG) -c -emit-llvm $(CFLAGS) $< -o - | 
    $(LLC) -march=bpf -filetype=obj -o $@

.PHONY: all clean

all: xdp_filter.o

clean:
    rm -f ./*.o

KDIR ಕರ್ನಲ್ ಹೆಡರ್‌ಗಳಿಗೆ ಮಾರ್ಗವನ್ನು ಒಳಗೊಂಡಿದೆ, ARCH - ಸಿಸ್ಟಮ್ ಆರ್ಕಿಟೆಕ್ಚರ್. ವಿತರಣೆಗಳ ನಡುವೆ ಮಾರ್ಗಗಳು ಮತ್ತು ಉಪಕರಣಗಳು ಸ್ವಲ್ಪ ಬದಲಾಗಬಹುದು.

ಡೆಬಿಯನ್ 10 (ಕರ್ನಲ್ 4.19.67) ಗಾಗಿ ವ್ಯತ್ಯಾಸಗಳ ಉದಾಹರಣೆ

# другая команда
CLANG ?= clang
LLC ?= llc-7

# другой каталог
KDIR ?= /usr/src/linux-headers-$(shell uname -r)
ARCH ?= $(subst x86_64,x86,$(shell uname -m))

# два дополнительных каталога -I
CFLAGS = 
    -Ihelpers 
    
    -I/usr/src/linux-headers-4.19.0-6-common/include 
    -I/usr/src/linux-headers-4.19.0-6-common/arch/$(ARCH)/include 
    # далее без изменений

CFLAGS ಸಹಾಯಕ ಹೆಡರ್‌ಗಳೊಂದಿಗೆ ಡೈರೆಕ್ಟರಿಯನ್ನು ಮತ್ತು ಕರ್ನಲ್ ಹೆಡರ್‌ಗಳೊಂದಿಗೆ ಹಲವಾರು ಡೈರೆಕ್ಟರಿಗಳನ್ನು ಸಂಪರ್ಕಿಸಿ. ಚಿಹ್ನೆ __KERNEL__ ಅಂದರೆ UAPI (userspace API) ಹೆಡರ್‌ಗಳನ್ನು ಕರ್ನಲ್ ಕೋಡ್‌ಗಾಗಿ ವ್ಯಾಖ್ಯಾನಿಸಲಾಗಿದೆ, ಏಕೆಂದರೆ ಫಿಲ್ಟರ್ ಅನ್ನು ಕರ್ನಲ್‌ನಲ್ಲಿ ಕಾರ್ಯಗತಗೊಳಿಸಲಾಗುತ್ತದೆ.

ಸ್ಟಾಕ್ ರಕ್ಷಣೆಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಬಹುದು (-fno-stack-protector), ಏಕೆಂದರೆ eBPF ಕೋಡ್ ಪರಿಶೀಲಕವು ಸ್ಟಾಕ್-ಆಫ್-ಬೌಂಡ್ಸ್ ಉಲ್ಲಂಘನೆಗಳಿಗಾಗಿ ಇನ್ನೂ ಪರಿಶೀಲಿಸುತ್ತದೆ. ಈಗಿನಿಂದಲೇ ಆಪ್ಟಿಮೈಸೇಶನ್‌ಗಳನ್ನು ಆನ್ ಮಾಡುವುದು ಯೋಗ್ಯವಾಗಿದೆ, ಏಕೆಂದರೆ eBPF ಬೈಟ್‌ಕೋಡ್‌ನ ಗಾತ್ರವು ಸೀಮಿತವಾಗಿದೆ.

ಎಲ್ಲಾ ಪ್ಯಾಕೆಟ್‌ಗಳನ್ನು ಹಾದುಹೋಗುವ ಮತ್ತು ಏನನ್ನೂ ಮಾಡದ ಫಿಲ್ಟರ್‌ನೊಂದಿಗೆ ಪ್ರಾರಂಭಿಸೋಣ:

#include <uapi/linux/bpf.h>

#include <bpf_helpers.h>

SEC("prog")
int xdp_main(struct xdp_md* ctx) {
    return XDP_PASS;
}

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

ತಂಡದ make ಸಂಗ್ರಹಿಸುತ್ತದೆ xdp_filter.o. ಈಗ ಅದನ್ನು ಎಲ್ಲಿ ಪ್ರಯತ್ನಿಸಬೇಕು?

ಪರೀಕ್ಷಾ ನಿಲುವು

ಸ್ಟ್ಯಾಂಡ್ ಎರಡು ಇಂಟರ್ಫೇಸ್ಗಳನ್ನು ಒಳಗೊಂಡಿರಬೇಕು: ಅದರಲ್ಲಿ ಫಿಲ್ಟರ್ ಇರುತ್ತದೆ ಮತ್ತು ಯಾವ ಪ್ಯಾಕೆಟ್ಗಳಿಂದ ಕಳುಹಿಸಲಾಗುತ್ತದೆ. ನಮ್ಮ ಫಿಲ್ಟರ್‌ನೊಂದಿಗೆ ನಿಯಮಿತ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಹೇಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತವೆ ಎಂಬುದನ್ನು ಪರಿಶೀಲಿಸಲು ಇವುಗಳು ತಮ್ಮ ಸ್ವಂತ IPಗಳೊಂದಿಗೆ ಪೂರ್ಣ ಪ್ರಮಾಣದ Linux ಸಾಧನಗಳಾಗಿರಬೇಕು.

ವೆಥ್ (ವರ್ಚುವಲ್ ಎತರ್ನೆಟ್) ಪ್ರಕಾರದ ಸಾಧನಗಳು ನಮಗೆ ಸೂಕ್ತವಾಗಿವೆ: ಇವುಗಳು ಪರಸ್ಪರ ನೇರವಾಗಿ "ಸಂಪರ್ಕಿಸಲಾದ" ವರ್ಚುವಲ್ ನೆಟ್‌ವರ್ಕ್ ಇಂಟರ್‌ಫೇಸ್‌ಗಳ ಜೋಡಿ. ನೀವು ಅವುಗಳನ್ನು ಈ ರೀತಿ ರಚಿಸಬಹುದು (ಈ ವಿಭಾಗದಲ್ಲಿ ಎಲ್ಲಾ ಆಜ್ಞೆಗಳು ip ನಿಂದ ಕೈಗೊಳ್ಳಲಾಗುತ್ತದೆ root):

ip link add xdp-remote type veth peer name xdp-local

ಇದು xdp-remote и xdp-local - ಸಾಧನದ ಹೆಸರುಗಳು. ಆನ್ xdp-local (192.0.2.1/24) ಜೊತೆಗೆ ಫಿಲ್ಟರ್ ಅನ್ನು ಲಗತ್ತಿಸಲಾಗಿದೆ xdp-remote (192.0.2.2/24) ಒಳಬರುವ ಸಂಚಾರವನ್ನು ಕಳುಹಿಸಲಾಗುತ್ತದೆ. ಆದಾಗ್ಯೂ, ಒಂದು ಸಮಸ್ಯೆ ಇದೆ: ಇಂಟರ್ಫೇಸ್‌ಗಳು ಒಂದೇ ಗಣಕದಲ್ಲಿವೆ ಮತ್ತು ಲಿನಕ್ಸ್ ಅವುಗಳಲ್ಲಿ ಒಂದಕ್ಕೆ ಟ್ರಾಫಿಕ್ ಅನ್ನು ಇನ್ನೊಂದರ ಮೂಲಕ ಕಳುಹಿಸುವುದಿಲ್ಲ. ನೀವು ಟ್ರಿಕಿ ನಿಯಮಗಳೊಂದಿಗೆ ಇದನ್ನು ಪರಿಹರಿಸಬಹುದು iptables, ಆದರೆ ಅವರು ಪ್ಯಾಕೇಜುಗಳನ್ನು ಬದಲಾಯಿಸಬೇಕಾಗುತ್ತದೆ, ಇದು ಡೀಬಗ್ ಮಾಡಲು ಅನಾನುಕೂಲವಾಗಿದೆ. ನೆಟ್‌ವರ್ಕ್ ನೇಮ್‌ಸ್ಪೇಸ್‌ಗಳನ್ನು ಬಳಸುವುದು ಉತ್ತಮ (ಇನ್ನು ಮುಂದೆ ನೆಟ್ನ್ಸ್).

ನೆಟ್‌ವರ್ಕ್ ನೇಮ್‌ಸ್ಪೇಸ್ ಇಂಟರ್‌ಫೇಸ್‌ಗಳು, ರೂಟಿಂಗ್ ಟೇಬಲ್‌ಗಳು ಮತ್ತು ನೆಟ್‌ಫಿಲ್ಟರ್ ನಿಯಮಗಳನ್ನು ಇತರ ನೆಟ್‌ಗಳಲ್ಲಿ ಒಂದೇ ರೀತಿಯ ವಸ್ತುಗಳಿಂದ ಪ್ರತ್ಯೇಕಿಸುತ್ತದೆ. ಪ್ರತಿಯೊಂದು ಪ್ರಕ್ರಿಯೆಯು ನೇಮ್‌ಸ್ಪೇಸ್‌ನಲ್ಲಿ ಸಾಗುತ್ತದೆ ಮತ್ತು ಆ ನೆಟ್‌ಗಳ ವಸ್ತುಗಳಿಗೆ ಮಾತ್ರ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿರುತ್ತದೆ. ಪೂರ್ವನಿಯೋಜಿತವಾಗಿ, ಸಿಸ್ಟಮ್ ಎಲ್ಲಾ ಆಬ್ಜೆಕ್ಟ್‌ಗಳಿಗೆ ಒಂದೇ ನೆಟ್‌ವರ್ಕ್ ನೇಮ್‌ಸ್ಪೇಸ್ ಅನ್ನು ಹೊಂದಿದೆ, ಆದ್ದರಿಂದ ನೀವು ಲಿನಕ್ಸ್‌ನಲ್ಲಿ ಕೆಲಸ ಮಾಡಬಹುದು ಮತ್ತು ನೆಟ್‌ನ್‌ಗಳ ಬಗ್ಗೆ ತಿಳಿದಿಲ್ಲ.

ಹೊಸ ನೇಮ್‌ಸ್ಪೇಸ್ ಅನ್ನು ರಚಿಸೋಣ xdp-test ಮತ್ತು ಅದನ್ನು ಅಲ್ಲಿಗೆ ಸರಿಸಿ xdp-remote.

ip netns add xdp-test
ip link set dev xdp-remote netns xdp-test

ನಂತರ ಪ್ರಕ್ರಿಯೆಯು ಚಾಲನೆಯಲ್ಲಿದೆ xdp-test, "ನೋಡುವುದಿಲ್ಲ" xdp-local (ಇದು ಪೂರ್ವನಿಯೋಜಿತವಾಗಿ ನೆಟ್‌ಗಳಲ್ಲಿ ಉಳಿಯುತ್ತದೆ) ಮತ್ತು ಪ್ಯಾಕೆಟ್ ಅನ್ನು 192.0.2.1 ಗೆ ಕಳುಹಿಸುವಾಗ ಅದು ಅದರ ಮೂಲಕ ಹಾದುಹೋಗುತ್ತದೆ xdp-remoteಏಕೆಂದರೆ ಇದು 192.0.2.0/24 ನಲ್ಲಿ ಈ ಪ್ರಕ್ರಿಯೆಗೆ ಪ್ರವೇಶಿಸಬಹುದಾದ ಏಕೈಕ ಇಂಟರ್ಫೇಸ್ ಆಗಿದೆ. ಇದು ವಿರುದ್ಧ ದಿಕ್ಕಿನಲ್ಲಿಯೂ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ.

Netns ನಡುವೆ ಚಲಿಸುವಾಗ, ಇಂಟರ್ಫೇಸ್ ಕೆಳಗೆ ಹೋಗುತ್ತದೆ ಮತ್ತು ಅದರ ವಿಳಾಸವನ್ನು ಕಳೆದುಕೊಳ್ಳುತ್ತದೆ. ಇಂಟರ್ಫೇಸ್ ಅನ್ನು netns ನಲ್ಲಿ ಕಾನ್ಫಿಗರ್ ಮಾಡಲು, ನೀವು ರನ್ ಮಾಡಬೇಕಾಗುತ್ತದೆ ip ... ಈ ಆಜ್ಞೆಯ ನೇಮ್‌ಸ್ಪೇಸ್‌ನಲ್ಲಿ ip netns exec:

ip netns exec xdp-test 
    ip address add 192.0.2.2/24 dev xdp-remote
ip netns exec xdp-test 
    ip link set xdp-remote up

ನೀವು ನೋಡುವಂತೆ, ಇದು ಸೆಟ್ಟಿಂಗ್‌ಗಿಂತ ಭಿನ್ನವಾಗಿಲ್ಲ xdp-local ಡೀಫಾಲ್ಟ್ ನೇಮ್‌ಸ್ಪೇಸ್‌ನಲ್ಲಿ:

    ip address add 192.0.2.1/24 dev xdp-local
    ip link set xdp-local up

ನೀವು ಓಡಿದರೆ tcpdump -tnevi xdp-local, ಪ್ಯಾಕೆಟ್‌ಗಳನ್ನು ಕಳುಹಿಸಿರುವುದನ್ನು ನೀವು ನೋಡಬಹುದು xdp-test, ಈ ಇಂಟರ್ಫೇಸ್‌ಗೆ ತಲುಪಿಸಲಾಗುತ್ತದೆ:

ip netns exec xdp-test   ping 192.0.2.1

ಶೆಲ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲು ಇದು ಅನುಕೂಲಕರವಾಗಿದೆ xdp-test. ರೆಪೊಸಿಟರಿಯು ಸ್ಟ್ಯಾಂಡ್‌ನೊಂದಿಗೆ ಕೆಲಸವನ್ನು ಸ್ವಯಂಚಾಲಿತಗೊಳಿಸುವ ಸ್ಕ್ರಿಪ್ಟ್ ಅನ್ನು ಹೊಂದಿದೆ; ಉದಾಹರಣೆಗೆ, ನೀವು ಆಜ್ಞೆಯೊಂದಿಗೆ ಸ್ಟ್ಯಾಂಡ್ ಅನ್ನು ಕಾನ್ಫಿಗರ್ ಮಾಡಬಹುದು sudo ./stand up ಮತ್ತು ಅದನ್ನು ಅಳಿಸಿ sudo ./stand down.

ಟ್ರೇಸಿಂಗ್

ಫಿಲ್ಟರ್ ಈ ರೀತಿಯ ಸಾಧನದೊಂದಿಗೆ ಸಂಯೋಜಿತವಾಗಿದೆ:

ip -force link set dev xdp-local xdp object xdp_filter.o verbose

ಕೀ -force ಇನ್ನೊಂದು ಪ್ರೋಗ್ರಾಂ ಈಗಾಗಲೇ ಲಿಂಕ್ ಆಗಿದ್ದರೆ ಹೊಸ ಪ್ರೋಗ್ರಾಂ ಅನ್ನು ಲಿಂಕ್ ಮಾಡುವ ಅಗತ್ಯವಿದೆ. "ಯಾವುದೇ ಸುದ್ದಿ ಒಳ್ಳೆಯ ಸುದ್ದಿ ಅಲ್ಲ" ಈ ಆಜ್ಞೆಯ ಬಗ್ಗೆ ಅಲ್ಲ, ಯಾವುದೇ ಸಂದರ್ಭದಲ್ಲಿ ತೀರ್ಮಾನವು ದೊಡ್ಡದಾಗಿದೆ. ಸೂಚಿಸುತ್ತವೆ verbose ಐಚ್ಛಿಕ, ಆದರೆ ಅದರೊಂದಿಗೆ ಅಸೆಂಬ್ಲಿ ಪಟ್ಟಿಯೊಂದಿಗೆ ಕೋಡ್ ವೆರಿಫೈಯರ್‌ನ ಕೆಲಸದ ಕುರಿತು ವರದಿಯು ಕಾಣಿಸಿಕೊಳ್ಳುತ್ತದೆ:

Verifier analysis:

0: (b7) r0 = 2
1: (95) exit

ಇಂಟರ್ಫೇಸ್ನಿಂದ ಪ್ರೋಗ್ರಾಂ ಅನ್ನು ಅನ್ಲಿಂಕ್ ಮಾಡಿ:

ip link set dev xdp-local xdp off

ಲಿಪಿಯಲ್ಲಿ ಇವು ಆಜ್ಞೆಗಳಾಗಿವೆ sudo ./stand attach и sudo ./stand detach.

ಫಿಲ್ಟರ್ ಅನ್ನು ಲಗತ್ತಿಸುವ ಮೂಲಕ, ನೀವು ಅದನ್ನು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಬಹುದು ping ಚಾಲನೆಯಲ್ಲಿದೆ, ಆದರೆ ಪ್ರೋಗ್ರಾಂ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆಯೇ? ಲಾಗ್‌ಗಳನ್ನು ಸೇರಿಸೋಣ. ಕಾರ್ಯ bpf_trace_printk() ಹೋಲುತ್ತದೆ printf(), ಆದರೆ ಪ್ಯಾಟರ್ನ್ ಹೊರತುಪಡಿಸಿ ಮೂರು ಆರ್ಗ್ಯುಮೆಂಟ್‌ಗಳನ್ನು ಮತ್ತು ನಿರ್ದಿಷ್ಟಪಡಿಸುವ ಸೀಮಿತ ಪಟ್ಟಿಯನ್ನು ಮಾತ್ರ ಬೆಂಬಲಿಸುತ್ತದೆ. ಮ್ಯಾಕ್ರೋ bpf_printk() ಕರೆಯನ್ನು ಸರಳಗೊಳಿಸುತ್ತದೆ.

   SEC("prog")
   int xdp_main(struct xdp_md* ctx) {
+      bpf_printk("got packet: %pn", ctx);
       return XDP_PASS;
   }

ಔಟ್‌ಪುಟ್ ಕರ್ನಲ್ ಟ್ರೇಸ್ ಚಾನಲ್‌ಗೆ ಹೋಗುತ್ತದೆ, ಅದನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಬೇಕಾಗಿದೆ:

echo -n 1 | sudo tee /sys/kernel/debug/tracing/options/trace_printk

ಸಂದೇಶದ ಎಳೆಯನ್ನು ವೀಕ್ಷಿಸಿ:

cat /sys/kernel/debug/tracing/trace_pipe

ಈ ಎರಡೂ ಆಜ್ಞೆಗಳು ಕರೆ ಮಾಡುತ್ತವೆ sudo ./stand log.

ಪಿಂಗ್ ಈಗ ಈ ರೀತಿಯ ಸಂದೇಶಗಳನ್ನು ಪ್ರಚೋದಿಸಬೇಕು:

<...>-110930 [004] ..s1 78803.244967: 0: got packet: 00000000ac510377

ನೀವು ಪರಿಶೀಲಕನ ಔಟ್‌ಪುಟ್ ಅನ್ನು ಹತ್ತಿರದಿಂದ ನೋಡಿದರೆ, ನೀವು ವಿಚಿತ್ರ ಲೆಕ್ಕಾಚಾರಗಳನ್ನು ಗಮನಿಸಬಹುದು:

0: (bf) r3 = r1
1: (18) r1 = 0xa7025203a7465
3: (7b) *(u64 *)(r10 -8) = r1
4: (18) r1 = 0x6b63617020746f67
6: (7b) *(u64 *)(r10 -16) = r1
7: (bf) r1 = r10
8: (07) r1 += -16
9: (b7) r2 = 16
10: (85) call bpf_trace_printk#6
<...>

ಸತ್ಯವೆಂದರೆ eBPF ಪ್ರೋಗ್ರಾಂಗಳು ಡೇಟಾ ವಿಭಾಗವನ್ನು ಹೊಂದಿಲ್ಲ, ಆದ್ದರಿಂದ ಫಾರ್ಮ್ಯಾಟ್ ಸ್ಟ್ರಿಂಗ್ ಅನ್ನು ಎನ್ಕೋಡ್ ಮಾಡುವ ಏಕೈಕ ಮಾರ್ಗವೆಂದರೆ VM ಆಜ್ಞೆಗಳ ತಕ್ಷಣದ ವಾದಗಳು:

$ python -c "import binascii; print(bytes(reversed(binascii.unhexlify('0a7025203a74656b63617020746f67'))))"
b'got packet: %pn'

ಈ ಕಾರಣಕ್ಕಾಗಿ, ಡೀಬಗ್ ಔಟ್ಪುಟ್ ಪರಿಣಾಮವಾಗಿ ಕೋಡ್ ಅನ್ನು ಹೆಚ್ಚು ಉಬ್ಬುತ್ತದೆ.

XDP ಪ್ಯಾಕೆಟ್‌ಗಳನ್ನು ಕಳುಹಿಸಲಾಗುತ್ತಿದೆ

ಫಿಲ್ಟರ್ ಅನ್ನು ಬದಲಾಯಿಸೋಣ: ಎಲ್ಲಾ ಒಳಬರುವ ಪ್ಯಾಕೆಟ್‌ಗಳನ್ನು ಹಿಂತಿರುಗಿಸೋಣ. ನೆಟ್‌ವರ್ಕ್ ದೃಷ್ಟಿಕೋನದಿಂದ ಇದು ತಪ್ಪಾಗಿದೆ, ಏಕೆಂದರೆ ಹೆಡರ್‌ಗಳಲ್ಲಿನ ವಿಳಾಸಗಳನ್ನು ಬದಲಾಯಿಸುವುದು ಅಗತ್ಯವಾಗಿರುತ್ತದೆ, ಆದರೆ ಈಗ ತಾತ್ವಿಕವಾಗಿ ಕೆಲಸವು ಮುಖ್ಯವಾಗಿದೆ.

       bpf_printk("got packet: %pn", ctx);
-      return XDP_PASS;
+      return XDP_TX;
   }

ಆರಂಭ tcpdump ಮೇಲೆ xdp-remote. ಇದು ಒಂದೇ ರೀತಿಯ ಹೊರಹೋಗುವ ಮತ್ತು ಒಳಬರುವ ICMP ಎಕೋ ವಿನಂತಿಯನ್ನು ತೋರಿಸಬೇಕು ಮತ್ತು ICMP ಎಕೋ ಪ್ರತ್ಯುತ್ತರವನ್ನು ತೋರಿಸುವುದನ್ನು ನಿಲ್ಲಿಸಬೇಕು. ಆದರೆ ಅದು ತೋರಿಸುವುದಿಲ್ಲ. ಇದು ಕೆಲಸಕ್ಕಾಗಿ ಎಂದು ತಿರುಗುತ್ತದೆ XDP_TX ಕಾರ್ಯಕ್ರಮದಲ್ಲಿ xdp-local ಅಗತ್ಯಜೋಡಿ ಇಂಟರ್ಫೇಸ್ಗೆ xdp-remote ಒಂದು ಕಾರ್ಯಕ್ರಮವನ್ನು ಸಹ ನಿಯೋಜಿಸಲಾಯಿತು, ಅದು ಖಾಲಿಯಾಗಿದ್ದರೂ, ಮತ್ತು ಅವನನ್ನು ಬೆಳೆಸಲಾಯಿತು.

ಇದು ನನಗೆ ಹೇಗೆ ಗೊತ್ತಾಯಿತು?

ಕರ್ನಲ್‌ನಲ್ಲಿ ಪ್ಯಾಕೇಜ್‌ನ ಮಾರ್ಗವನ್ನು ಪತ್ತೆಹಚ್ಚಿ perf ಈವೆಂಟ್‌ಗಳ ಕಾರ್ಯವಿಧಾನವು ಅದೇ ವರ್ಚುವಲ್ ಯಂತ್ರವನ್ನು ಬಳಸಲು ಅನುಮತಿಸುತ್ತದೆ, ಅಂದರೆ eBPF ಅನ್ನು eBPF ನೊಂದಿಗೆ ಡಿಸ್ಅಸೆಂಬಲ್ ಮಾಡಲು ಬಳಸಲಾಗುತ್ತದೆ.

ನೀವು ಕೆಟ್ಟದ್ದನ್ನು ಒಳ್ಳೆಯದನ್ನು ಮಾಡಬೇಕು, ಏಕೆಂದರೆ ಅದನ್ನು ಮಾಡಲು ಬೇರೆ ಏನೂ ಇಲ್ಲ.

$ sudo perf trace --call-graph dwarf -e 'xdp:*'
   0.000 ping/123455 xdp:xdp_bulk_tx:ifindex=19 action=TX sent=0 drops=1 err=-6
                                     veth_xdp_flush_bq ([veth])
                                     veth_xdp_flush_bq ([veth])
                                     veth_poll ([veth])
                                     <...>

ಕೋಡ್ 6 ಎಂದರೇನು?

$ errno 6
ENXIO 6 No such device or address

ಕಾರ್ಯ veth_xdp_flush_bq() ನಿಂದ ದೋಷ ಕೋಡ್ ಸ್ವೀಕರಿಸುತ್ತದೆ veth_xdp_xmit(), ಅಲ್ಲಿ ಹುಡುಕಾಟ ENXIO ಮತ್ತು ಕಾಮೆಂಟ್ ಅನ್ನು ಹುಡುಕಿ.

ಕನಿಷ್ಠ ಫಿಲ್ಟರ್ ಅನ್ನು ಮರುಸ್ಥಾಪಿಸೋಣ (XDP_PASS) ಕಡತದಲ್ಲಿ xdp_dummy.c, ಅದನ್ನು ಮೇಕ್‌ಫೈಲ್‌ಗೆ ಸೇರಿಸಿ, ಅದನ್ನು ಬಂಧಿಸಿ xdp-remote:

ip netns exec remote 
    ip link set dev int xdp object dummy.o

ಈಗ tcpdump ಏನನ್ನು ನಿರೀಕ್ಷಿಸಲಾಗಿದೆ ಎಂಬುದನ್ನು ತೋರಿಸುತ್ತದೆ:

62:57:8e:70:44:64 > 26:0e:25:37:8f:96, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 13762, offset 0, flags [DF], proto ICMP (1), length 84)
    192.0.2.2 > 192.0.2.1: ICMP echo request, id 46966, seq 1, length 64
62:57:8e:70:44:64 > 26:0e:25:37:8f:96, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 13762, offset 0, flags [DF], proto ICMP (1), length 84)
    192.0.2.2 > 192.0.2.1: ICMP echo request, id 46966, seq 1, length 64

ಬದಲಿಗೆ ARP ಗಳನ್ನು ಮಾತ್ರ ತೋರಿಸಿದರೆ, ನೀವು ಫಿಲ್ಟರ್‌ಗಳನ್ನು ತೆಗೆದುಹಾಕಬೇಕಾಗುತ್ತದೆ (ಇದು ಮಾಡುತ್ತದೆ sudo ./stand detach), ಬಿಡು ping, ನಂತರ ಫಿಲ್ಟರ್‌ಗಳನ್ನು ಹೊಂದಿಸಿ ಮತ್ತು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ. ಸಮಸ್ಯೆ ಎಂದರೆ ಫಿಲ್ಟರ್ XDP_TX ARP ಮತ್ತು ಸ್ಟಾಕ್ ಎರಡರಲ್ಲೂ ಮಾನ್ಯವಾಗಿರುತ್ತದೆ
ನಾಮಸ್ಥಳಗಳು xdp-test MAC ವಿಳಾಸ 192.0.2.1 ಅನ್ನು "ಮರೆಯಲು" ನಿರ್ವಹಿಸಲಾಗಿದೆ, ಈ IP ಅನ್ನು ಪರಿಹರಿಸಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ.

ಸಮಸ್ಯೆ ಹೇಳಿಕೆ

ಹೇಳಲಾದ ಕಾರ್ಯಕ್ಕೆ ಹೋಗೋಣ: XDP ನಲ್ಲಿ SYN ಕುಕೀಸ್ ಕಾರ್ಯವಿಧಾನವನ್ನು ಬರೆಯಿರಿ.

SYN ಪ್ರವಾಹವು ಜನಪ್ರಿಯ DDoS ದಾಳಿಯಾಗಿ ಉಳಿದಿದೆ, ಅದರ ಸಾರವು ಈ ಕೆಳಗಿನಂತಿರುತ್ತದೆ. ಸಂಪರ್ಕವನ್ನು ಸ್ಥಾಪಿಸಿದಾಗ (TCP ಹ್ಯಾಂಡ್‌ಶೇಕ್), ಸರ್ವರ್ SYN ಅನ್ನು ಸ್ವೀಕರಿಸುತ್ತದೆ, ಭವಿಷ್ಯದ ಸಂಪರ್ಕಕ್ಕಾಗಿ ಸಂಪನ್ಮೂಲಗಳನ್ನು ನಿಯೋಜಿಸುತ್ತದೆ, SYNACK ಪ್ಯಾಕೆಟ್‌ನೊಂದಿಗೆ ಪ್ರತಿಕ್ರಿಯಿಸುತ್ತದೆ ಮತ್ತು ACK ಗಾಗಿ ಕಾಯುತ್ತದೆ. ಆಕ್ರಮಣಕಾರರು ಪ್ರತಿ ಸೆಕೆಂಡಿಗೆ ಸಾವಿರಾರು SYN ಪ್ಯಾಕೆಟ್‌ಗಳನ್ನು ಪ್ರತಿ ಹೋಸ್ಟ್‌ನಿಂದ ವಂಚಿತ ವಿಳಾಸಗಳಿಂದ ಬಹು-ಸಾವಿರ-ಬಲವಾದ ಬೋಟ್‌ನೆಟ್‌ನಲ್ಲಿ ಕಳುಹಿಸುತ್ತಾರೆ. ಪ್ಯಾಕೆಟ್ ಆಗಮನದ ತಕ್ಷಣ ಸಂಪನ್ಮೂಲಗಳನ್ನು ನಿಯೋಜಿಸಲು ಸರ್ವರ್ ಒತ್ತಾಯಿಸಲ್ಪಡುತ್ತದೆ, ಆದರೆ ಹೆಚ್ಚಿನ ಸಮಯ ಮೀರಿದ ನಂತರ ಅವುಗಳನ್ನು ಬಿಡುಗಡೆ ಮಾಡುತ್ತದೆ; ಪರಿಣಾಮವಾಗಿ, ಮೆಮೊರಿ ಅಥವಾ ಮಿತಿಗಳು ಖಾಲಿಯಾಗುತ್ತವೆ, ಹೊಸ ಸಂಪರ್ಕಗಳನ್ನು ಸ್ವೀಕರಿಸಲಾಗುವುದಿಲ್ಲ ಮತ್ತು ಸೇವೆಯು ಲಭ್ಯವಿಲ್ಲ.

ನೀವು SYN ಪ್ಯಾಕೆಟ್ ಅನ್ನು ಆಧರಿಸಿ ಸಂಪನ್ಮೂಲಗಳನ್ನು ನಿಯೋಜಿಸದಿದ್ದರೆ, ಆದರೆ SYNACK ಪ್ಯಾಕೆಟ್‌ನೊಂದಿಗೆ ಮಾತ್ರ ಪ್ರತಿಕ್ರಿಯಿಸಿದರೆ, ನಂತರ ಬಂದ ACK ಪ್ಯಾಕೆಟ್ ಉಳಿಸದ SYN ಪ್ಯಾಕೆಟ್ ಅನ್ನು ಉಲ್ಲೇಖಿಸುತ್ತದೆ ಎಂದು ಸರ್ವರ್ ಹೇಗೆ ಅರ್ಥಮಾಡಿಕೊಳ್ಳಬಹುದು? ಎಲ್ಲಾ ನಂತರ, ಆಕ್ರಮಣಕಾರರು ನಕಲಿ ACK ಗಳನ್ನು ಸಹ ರಚಿಸಬಹುದು. SYN ಕುಕಿಯ ಅಂಶವು ಅದನ್ನು ಎನ್ಕೋಡ್ ಮಾಡುವುದು seqnum ಸಂಪರ್ಕ ನಿಯತಾಂಕಗಳು ವಿಳಾಸಗಳು, ಬಂದರುಗಳು ಮತ್ತು ಉಪ್ಪು ಬದಲಾಯಿಸುವ ಹ್ಯಾಶ್ ಆಗಿ. ಉಪ್ಪನ್ನು ಬದಲಾಯಿಸುವ ಮೊದಲು ACK ಆಗಮಿಸಿದರೆ, ನೀವು ಮತ್ತೆ ಹ್ಯಾಶ್ ಅನ್ನು ಲೆಕ್ಕ ಹಾಕಬಹುದು ಮತ್ತು ಅದನ್ನು ಹೋಲಿಸಬಹುದು acknum. ಫೋರ್ಜ್ acknum ಉಪ್ಪು ರಹಸ್ಯವನ್ನು ಒಳಗೊಂಡಿರುವುದರಿಂದ ಆಕ್ರಮಣಕಾರರಿಗೆ ಸಾಧ್ಯವಿಲ್ಲ ಮತ್ತು ಸೀಮಿತ ಚಾನಲ್‌ನಿಂದ ಅದರ ಮೂಲಕ ವಿಂಗಡಿಸಲು ಸಮಯವಿರುವುದಿಲ್ಲ.

SYN ಕುಕೀಯನ್ನು ಲಿನಕ್ಸ್ ಕರ್ನಲ್‌ನಲ್ಲಿ ದೀರ್ಘಕಾಲ ಅಳವಡಿಸಲಾಗಿದೆ ಮತ್ತು SYN ಗಳು ತುಂಬಾ ವೇಗವಾಗಿ ಮತ್ತು ಸಾಮೂಹಿಕವಾಗಿ ಬಂದರೆ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಸಹ ಸಕ್ರಿಯಗೊಳಿಸಬಹುದು.

TCP ಹ್ಯಾಂಡ್ಶೇಕ್ನಲ್ಲಿ ಶೈಕ್ಷಣಿಕ ಕಾರ್ಯಕ್ರಮ

TCP ಬೈಟ್‌ಗಳ ಸ್ಟ್ರೀಮ್ ಆಗಿ ಡೇಟಾ ಪ್ರಸರಣವನ್ನು ಒದಗಿಸುತ್ತದೆ, ಉದಾಹರಣೆಗೆ, HTTP ವಿನಂತಿಗಳನ್ನು TCP ಮೂಲಕ ರವಾನಿಸಲಾಗುತ್ತದೆ. ಸ್ಟ್ರೀಮ್ ಪ್ಯಾಕೆಟ್ಗಳಲ್ಲಿ ತುಂಡುಗಳಲ್ಲಿ ಹರಡುತ್ತದೆ. ಎಲ್ಲಾ TCP ಪ್ಯಾಕೆಟ್‌ಗಳು ತಾರ್ಕಿಕ ಫ್ಲ್ಯಾಗ್‌ಗಳು ಮತ್ತು 32-ಬಿಟ್ ಅನುಕ್ರಮ ಸಂಖ್ಯೆಗಳನ್ನು ಹೊಂದಿವೆ:

  • ಧ್ವಜಗಳ ಸಂಯೋಜನೆಯು ನಿರ್ದಿಷ್ಟ ಪ್ಯಾಕೇಜ್‌ನ ಪಾತ್ರವನ್ನು ನಿರ್ಧರಿಸುತ್ತದೆ. SYN ಫ್ಲ್ಯಾಗ್ ಇದು ಸಂಪರ್ಕದಲ್ಲಿ ಕಳುಹಿಸುವವರ ಮೊದಲ ಪ್ಯಾಕೆಟ್ ಎಂದು ಸೂಚಿಸುತ್ತದೆ. ACK ಫ್ಲ್ಯಾಗ್ ಎಂದರೆ ಕಳುಹಿಸುವವರು ಬೈಟ್‌ವರೆಗಿನ ಎಲ್ಲಾ ಸಂಪರ್ಕ ಡೇಟಾವನ್ನು ಸ್ವೀಕರಿಸಿದ್ದಾರೆ ಎಂದರ್ಥ acknum. ಒಂದು ಪ್ಯಾಕೆಟ್ ಹಲವಾರು ಧ್ವಜಗಳನ್ನು ಹೊಂದಬಹುದು ಮತ್ತು ಅವುಗಳ ಸಂಯೋಜನೆಯಿಂದ ಕರೆಯಲಾಗುತ್ತದೆ, ಉದಾಹರಣೆಗೆ, ಸಿನಾಕ್ ಪ್ಯಾಕೆಟ್.

  • ಅನುಕ್ರಮ ಸಂಖ್ಯೆ (seqnum) ಈ ಪ್ಯಾಕೆಟ್‌ನಲ್ಲಿ ರವಾನೆಯಾಗುವ ಮೊದಲ ಬೈಟ್‌ಗಾಗಿ ಡೇಟಾ ಸ್ಟ್ರೀಮ್‌ನಲ್ಲಿ ಆಫ್‌ಸೆಟ್ ಅನ್ನು ನಿರ್ದಿಷ್ಟಪಡಿಸುತ್ತದೆ. ಉದಾಹರಣೆಗೆ, X ಬೈಟ್‌ಗಳ ಡೇಟಾದೊಂದಿಗೆ ಮೊದಲ ಪ್ಯಾಕೆಟ್‌ನಲ್ಲಿ ಈ ಸಂಖ್ಯೆ N ಆಗಿದ್ದರೆ, ಹೊಸ ಡೇಟಾದೊಂದಿಗೆ ಮುಂದಿನ ಪ್ಯಾಕೆಟ್‌ನಲ್ಲಿ ಅದು N+X ಆಗಿರುತ್ತದೆ. ಸಂಪರ್ಕದ ಆರಂಭದಲ್ಲಿ, ಪ್ರತಿ ಬದಿಯು ಈ ಸಂಖ್ಯೆಯನ್ನು ಯಾದೃಚ್ಛಿಕವಾಗಿ ಆಯ್ಕೆ ಮಾಡುತ್ತದೆ.

  • ಸ್ವೀಕೃತಿ ಸಂಖ್ಯೆ (ಅಕ್ನಮ್) - seqnum ನಂತೆಯೇ ಅದೇ ಆಫ್‌ಸೆಟ್, ಆದರೆ ಇದು ರವಾನೆಯಾಗುವ ಬೈಟ್‌ನ ಸಂಖ್ಯೆಯನ್ನು ನಿರ್ಧರಿಸುವುದಿಲ್ಲ, ಆದರೆ ಸ್ವೀಕರಿಸುವವರಿಂದ ಮೊದಲ ಬೈಟ್‌ನ ಸಂಖ್ಯೆಯನ್ನು ಕಳುಹಿಸುವವರು ನೋಡಲಿಲ್ಲ.

ಸಂಪರ್ಕದ ಆರಂಭದಲ್ಲಿ, ಪಕ್ಷಗಳು ಒಪ್ಪಿಕೊಳ್ಳಬೇಕು seqnum и acknum. ಕ್ಲೈಂಟ್ ಅದರೊಂದಿಗೆ SYN ಪ್ಯಾಕೆಟ್ ಅನ್ನು ಕಳುಹಿಸುತ್ತದೆ seqnum = X. ಸರ್ವರ್ ಸಿನಾಕ್ ಪ್ಯಾಕೆಟ್‌ನೊಂದಿಗೆ ಪ್ರತಿಕ್ರಿಯಿಸುತ್ತದೆ, ಅಲ್ಲಿ ಅದು ಅದನ್ನು ದಾಖಲಿಸುತ್ತದೆ seqnum = Y ಮತ್ತು ಒಡ್ಡುತ್ತದೆ acknum = X + 1. ಕ್ಲೈಂಟ್ ACK ಪ್ಯಾಕೆಟ್ನೊಂದಿಗೆ SYNACK ಗೆ ಪ್ರತಿಕ್ರಿಯಿಸುತ್ತದೆ, ಅಲ್ಲಿ seqnum = X + 1, acknum = Y + 1. ಇದರ ನಂತರ, ನಿಜವಾದ ಡೇಟಾ ವರ್ಗಾವಣೆ ಪ್ರಾರಂಭವಾಗುತ್ತದೆ.

ಪೀರ್ ಪ್ಯಾಕೆಟ್‌ನ ಸ್ವೀಕೃತಿಯನ್ನು ಅಂಗೀಕರಿಸದಿದ್ದರೆ, ಸಮಯ ಮೀರಿದ ನಂತರ TCP ಅದನ್ನು ಮರುಕಳುಹಿಸುತ್ತದೆ.

SYN ಕುಕೀಗಳನ್ನು ಯಾವಾಗಲೂ ಏಕೆ ಬಳಸಲಾಗುವುದಿಲ್ಲ?

ಮೊದಲನೆಯದಾಗಿ, SYNACK ಅಥವಾ ACK ಕಳೆದುಹೋದರೆ, ಅದನ್ನು ಮತ್ತೆ ಕಳುಹಿಸಲು ನೀವು ಕಾಯಬೇಕಾಗುತ್ತದೆ - ಸಂಪರ್ಕ ಸೆಟಪ್ ನಿಧಾನಗೊಳ್ಳುತ್ತದೆ. ಎರಡನೆಯದಾಗಿ, SYN ಪ್ಯಾಕೇಜ್‌ನಲ್ಲಿ - ಮತ್ತು ಅದರಲ್ಲಿ ಮಾತ್ರ! - ಸಂಪರ್ಕದ ಮುಂದಿನ ಕಾರ್ಯಾಚರಣೆಯ ಮೇಲೆ ಪರಿಣಾಮ ಬೀರುವ ಹಲವಾರು ಆಯ್ಕೆಗಳನ್ನು ರವಾನಿಸಲಾಗುತ್ತದೆ. ಒಳಬರುವ SYN ಪ್ಯಾಕೆಟ್‌ಗಳನ್ನು ನೆನಪಿಟ್ಟುಕೊಳ್ಳದೆ, ಸರ್ವರ್ ಈ ಆಯ್ಕೆಗಳನ್ನು ನಿರ್ಲಕ್ಷಿಸುತ್ತದೆ; ಕ್ಲೈಂಟ್ ಅವುಗಳನ್ನು ಮುಂದಿನ ಪ್ಯಾಕೆಟ್‌ಗಳಲ್ಲಿ ಕಳುಹಿಸುವುದಿಲ್ಲ. ಈ ಸಂದರ್ಭದಲ್ಲಿ ಟಿಸಿಪಿ ಕೆಲಸ ಮಾಡಬಹುದು, ಆದರೆ ಕನಿಷ್ಠ ಆರಂಭಿಕ ಹಂತದಲ್ಲಿ ಸಂಪರ್ಕದ ಗುಣಮಟ್ಟ ಕಡಿಮೆಯಾಗುತ್ತದೆ.

ಪ್ಯಾಕೇಜ್‌ಗಳ ವಿಷಯದಲ್ಲಿ, XDP ಪ್ರೋಗ್ರಾಂ ಈ ಕೆಳಗಿನವುಗಳನ್ನು ಮಾಡಬೇಕು:

  • ಕುಕೀಯೊಂದಿಗೆ SYNACK ನೊಂದಿಗೆ SYN ಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿ;
  • RST ಯೊಂದಿಗೆ ACK ಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿ (ಡಿಸ್ಕನೆಕ್ಟ್);
  • ಉಳಿದ ಪ್ಯಾಕೆಟ್‌ಗಳನ್ನು ತ್ಯಜಿಸಿ.

ಪ್ಯಾಕೇಜ್ ಪಾರ್ಸಿಂಗ್ ಜೊತೆಗೆ ಅಲ್ಗಾರಿದಮ್‌ನ ಸೂಡೊಕೋಡ್:

Если это не Ethernet,
    пропустить пакет.
Если это не IPv4,
    пропустить пакет.
Если адрес в таблице проверенных,               (*)
        уменьшить счетчик оставшихся проверок,
        пропустить пакет.
Если это не TCP,
    сбросить пакет.     (**)
Если это SYN,
    ответить SYN-ACK с cookie.
Если это ACK,
    если в acknum лежит не cookie,
        сбросить пакет.
    Занести в таблицу адрес с N оставшихся проверок.    (*)
    Ответить RST.   (**)
В остальных случаях сбросить пакет.

ಒಂದು (*) ಸಿಸ್ಟಂನ ಸ್ಥಿತಿಯನ್ನು ನೀವು ನಿರ್ವಹಿಸಬೇಕಾದ ಅಂಶಗಳನ್ನು ಗುರುತಿಸಲಾಗಿದೆ - ಮೊದಲ ಹಂತದಲ್ಲಿ ನೀವು ಸಿಕ್ನಮ್ ಆಗಿ SYN ಕುಕಿಯ ಉತ್ಪಾದನೆಯೊಂದಿಗೆ TCP ಹ್ಯಾಂಡ್‌ಶೇಕ್ ಅನ್ನು ಸರಳವಾಗಿ ಕಾರ್ಯಗತಗೊಳಿಸುವ ಮೂಲಕ ಅವುಗಳನ್ನು ಇಲ್ಲದೆ ಮಾಡಬಹುದು.

ತತ್ಕ್ಷಣ (**), ನಮ್ಮ ಬಳಿ ಟೇಬಲ್ ಇಲ್ಲದಿರುವಾಗ, ನಾವು ಪ್ಯಾಕೆಟ್ ಅನ್ನು ಬಿಟ್ಟುಬಿಡುತ್ತೇವೆ.

TCP ಹ್ಯಾಂಡ್ಶೇಕ್ ಅನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸಲಾಗುತ್ತಿದೆ

ಪ್ಯಾಕೇಜ್ ಅನ್ನು ಪಾರ್ಸ್ ಮಾಡುವುದು ಮತ್ತು ಕೋಡ್ ಅನ್ನು ಪರಿಶೀಲಿಸುವುದು

ನಮಗೆ ನೆಟ್‌ವರ್ಕ್ ಹೆಡರ್ ರಚನೆಗಳು ಬೇಕಾಗುತ್ತವೆ: ಎತರ್ನೆಟ್ (uapi/linux/if_ether.h), IPv4 (uapi/linux/ip.h) ಮತ್ತು TCP (uapi/linux/tcp.h) ಸಂಬಂಧಿಸಿದ ದೋಷಗಳಿಂದಾಗಿ ಎರಡನೆಯದನ್ನು ಸಂಪರ್ಕಿಸಲು ನನಗೆ ಸಾಧ್ಯವಾಗಲಿಲ್ಲ atomic64_t, ನಾನು ಕೋಡ್‌ಗೆ ಅಗತ್ಯವಾದ ವ್ಯಾಖ್ಯಾನಗಳನ್ನು ನಕಲಿಸಬೇಕಾಗಿತ್ತು.

ಕರ್ನಲ್‌ನಲ್ಲಿನ eBPF ಪರಿಶೀಲಕವು ಬ್ಯಾಕ್‌ಟ್ರ್ಯಾಕಿಂಗ್ ಅನ್ನು ನಿಷೇಧಿಸುವುದರಿಂದ, ವಾಸ್ತವವಾಗಿ, ಲೂಪ್‌ಗಳು ಮತ್ತು ಫಂಕ್ಷನ್ ಕರೆಗಳನ್ನು ನಿಷೇಧಿಸುವುದರಿಂದ, ಓದುವಿಕೆಗಾಗಿ C ನಲ್ಲಿ ಹೈಲೈಟ್ ಮಾಡಲಾದ ಎಲ್ಲಾ ಕಾರ್ಯಗಳನ್ನು ಕರೆಯ ಹಂತದಲ್ಲಿ ಇನ್‌ಲೈನ್ ಮಾಡಬೇಕು.

#define INTERNAL static __attribute__((always_inline))

ಮ್ಯಾಕ್ರೋ LOG() ಬಿಡುಗಡೆ ಬಿಲ್ಡ್‌ನಲ್ಲಿ ಮುದ್ರಣವನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸುತ್ತದೆ.

ಪ್ರೋಗ್ರಾಂ ಕಾರ್ಯಗಳ ಕನ್ವೇಯರ್ ಆಗಿದೆ. ಪ್ರತಿಯೊಂದೂ ಒಂದು ಪ್ಯಾಕೆಟ್ ಅನ್ನು ಪಡೆಯುತ್ತದೆ, ಅದರಲ್ಲಿ ಅನುಗುಣವಾದ ಮಟ್ಟದ ಹೆಡರ್ ಅನ್ನು ಹೈಲೈಟ್ ಮಾಡಲಾಗುತ್ತದೆ, ಉದಾಹರಣೆಗೆ, process_ether() ಅದು ತುಂಬುತ್ತದೆ ಎಂದು ನಿರೀಕ್ಷಿಸುತ್ತದೆ ether. ಕ್ಷೇತ್ರ ವಿಶ್ಲೇಷಣೆಯ ಫಲಿತಾಂಶಗಳ ಆಧಾರದ ಮೇಲೆ, ಕಾರ್ಯವು ಪ್ಯಾಕೆಟ್ ಅನ್ನು ಉನ್ನತ ಮಟ್ಟಕ್ಕೆ ರವಾನಿಸಬಹುದು. ಕ್ರಿಯೆಯ ಫಲಿತಾಂಶವು XDP ಕ್ರಿಯೆಯಾಗಿದೆ. ಸದ್ಯಕ್ಕೆ, SYN ಮತ್ತು ACK ಹ್ಯಾಂಡ್ಲರ್‌ಗಳು ಎಲ್ಲಾ ಪ್ಯಾಕೆಟ್‌ಗಳನ್ನು ರವಾನಿಸುತ್ತಾರೆ.

struct Packet {
    struct xdp_md* ctx;

    struct ethhdr* ether;
    struct iphdr* ip;
    struct tcphdr* tcp;
};

INTERNAL int process_tcp_syn(struct Packet* packet) { return XDP_PASS; }
INTERNAL int process_tcp_ack(struct Packet* packet) { return XDP_PASS; }
INTERNAL int process_tcp(struct Packet* packet) { ... }
INTERNAL int process_ip(struct Packet* packet) { ... }

INTERNAL int
process_ether(struct Packet* packet) {
    struct ethhdr* ether = packet->ether;

    LOG("Ether(proto=0x%x)", bpf_ntohs(ether->h_proto));

    if (ether->h_proto != bpf_ntohs(ETH_P_IP)) {
        return XDP_PASS;
    }

    // B
    struct iphdr* ip = (struct iphdr*)(ether + 1);
    if ((void*)(ip + 1) > (void*)packet->ctx->data_end) {
        return XDP_DROP; /* malformed packet */
    }

    packet->ip = ip;
    return process_ip(packet);
}

SEC("prog")
int xdp_main(struct xdp_md* ctx) {
    struct Packet packet;
    packet.ctx = ctx;

    // A
    struct ethhdr* ether = (struct ethhdr*)(void*)ctx->data;
    if ((void*)(ether + 1) > (void*)ctx->data_end) {
        return XDP_PASS;
    }

    packet.ether = ether;
    return process_ether(&packet);
}

ಎ ಮತ್ತು ಬಿ ಎಂದು ಗುರುತಿಸಲಾದ ಚೆಕ್‌ಗಳಿಗೆ ನಾನು ನಿಮ್ಮ ಗಮನವನ್ನು ಸೆಳೆಯುತ್ತೇನೆ. ನೀವು ಎ ಎಂದು ಕಾಮೆಂಟ್ ಮಾಡಿದರೆ, ಪ್ರೋಗ್ರಾಂ ನಿರ್ಮಿಸುತ್ತದೆ, ಆದರೆ ಲೋಡ್ ಮಾಡುವಾಗ ಪರಿಶೀಲನೆ ದೋಷವಿರುತ್ತದೆ:

Verifier analysis:

<...>
11: (7b) *(u64 *)(r10 -48) = r1
12: (71) r3 = *(u8 *)(r7 +13)
invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0)
R7 offset is outside of the packet
processed 11 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0

Error fetching program/map!

ಕೀ ಸ್ಟ್ರಿಂಗ್ invalid access to packet, off=13 size=1, R7(id=0,off=0,r=0): ಬಫರ್‌ನ ಆರಂಭದಿಂದ ಹದಿಮೂರನೇ ಬೈಟ್ ಪ್ಯಾಕೆಟ್‌ನ ಹೊರಗಿರುವಾಗ ಎಕ್ಸಿಕ್ಯೂಶನ್ ಪಥಗಳಿವೆ. ನಾವು ಯಾವ ಸಾಲಿನ ಬಗ್ಗೆ ಮಾತನಾಡುತ್ತಿದ್ದೇವೆ ಎಂಬುದನ್ನು ಪಟ್ಟಿಯಿಂದ ಅರ್ಥಮಾಡಿಕೊಳ್ಳುವುದು ಕಷ್ಟ, ಆದರೆ ಸೂಚನಾ ಸಂಖ್ಯೆ (12) ಮತ್ತು ಮೂಲ ಕೋಡ್‌ನ ಸಾಲುಗಳನ್ನು ತೋರಿಸುವ ಡಿಸ್ಅಸೆಂಬಲರ್ ಇದೆ:

llvm-objdump -S xdp_filter.o | less

ಈ ಸಂದರ್ಭದಲ್ಲಿ ಅದು ರೇಖೆಯನ್ನು ಸೂಚಿಸುತ್ತದೆ

LOG("Ether(proto=0x%x)", bpf_ntohs(ether->h_proto));

ಇದು ಸಮಸ್ಯೆ ಎಂದು ಸ್ಪಷ್ಟಪಡಿಸುತ್ತದೆ ether. ಇದು ಯಾವಾಗಲೂ ಹೀಗೆಯೇ ಇರುತ್ತಿತ್ತು.

SYN ಗೆ ಪ್ರತ್ಯುತ್ತರ ನೀಡಿ

ಈ ಹಂತದಲ್ಲಿ ಗುರಿಯು ಸ್ಥಿರದೊಂದಿಗೆ ಸರಿಯಾದ ಸಿನಾಕ್ ಪ್ಯಾಕೆಟ್ ಅನ್ನು ರಚಿಸುವುದು seqnum, ಇದು ಭವಿಷ್ಯದಲ್ಲಿ SYN ಕುಕೀಯಿಂದ ಬದಲಾಯಿಸಲ್ಪಡುತ್ತದೆ. ಎಲ್ಲಾ ಬದಲಾವಣೆಗಳು ಸಂಭವಿಸುತ್ತವೆ process_tcp_syn() ಮತ್ತು ಸುತ್ತಮುತ್ತಲಿನ ಪ್ರದೇಶಗಳು.

ಪ್ಯಾಕೇಜ್ ಪರಿಶೀಲನೆ

ವಿಚಿತ್ರವೆಂದರೆ, ಇಲ್ಲಿ ಅತ್ಯಂತ ಗಮನಾರ್ಹವಾದ ಸಾಲು, ಅಥವಾ ಅದರ ವ್ಯಾಖ್ಯಾನ:

/* Required to verify checksum calculation */
const void* data_end = (const void*)ctx->data_end;

ಕೋಡ್‌ನ ಮೊದಲ ಆವೃತ್ತಿಯನ್ನು ಬರೆಯುವಾಗ, 5.1 ಕರ್ನಲ್ ಅನ್ನು ಬಳಸಲಾಯಿತು, ಅದರ ಪರಿಶೀಲಕಕ್ಕೆ ವ್ಯತ್ಯಾಸವಿದೆ data_end и (const void*)ctx->data_end. ಬರೆಯುವ ಸಮಯದಲ್ಲಿ, ಕರ್ನಲ್ 5.3.1 ಈ ಸಮಸ್ಯೆಯನ್ನು ಹೊಂದಿರಲಿಲ್ಲ. ಕಂಪೈಲರ್ ಕ್ಷೇತ್ರಕ್ಕಿಂತ ವಿಭಿನ್ನವಾಗಿ ಸ್ಥಳೀಯ ವೇರಿಯಬಲ್ ಅನ್ನು ಪ್ರವೇಶಿಸುವ ಸಾಧ್ಯತೆಯಿದೆ. ಕಥೆಯ ನೈತಿಕತೆ: ಬಹಳಷ್ಟು ಗೂಡುಕಟ್ಟುವಿಕೆ ಇದ್ದಾಗ ಕೋಡ್ ಅನ್ನು ಸರಳಗೊಳಿಸುವುದು ಸಹಾಯ ಮಾಡುತ್ತದೆ.

ಮುಂದಿನದು ಪರಿಶೀಲಕನ ವೈಭವಕ್ಕಾಗಿ ವಾಡಿಕೆಯ ಉದ್ದದ ಪರಿಶೀಲನೆಗಳು; ಓ MAX_CSUM_BYTES ಕೆಳಗೆ.

const u32 ip_len = ip->ihl * 4;
if ((void*)ip + ip_len > data_end) {
    return XDP_DROP; /* malformed packet */
}
if (ip_len > MAX_CSUM_BYTES) {
    return XDP_ABORTED; /* implementation limitation */
}

const u32 tcp_len = tcp->doff * 4;
if ((void*)tcp + tcp_len > (void*)ctx->data_end) {
    return XDP_DROP; /* malformed packet */
}
if (tcp_len > MAX_CSUM_BYTES) {
    return XDP_ABORTED; /* implementation limitation */
}

ಪ್ಯಾಕೇಜ್ ಅನ್ನು ಬಿಚ್ಚಿಡಲಾಗುತ್ತಿದೆ

ನಾವು ಭರ್ತಿ ಮಾಡುತ್ತೇವೆ seqnum и acknum, ACK ಅನ್ನು ಹೊಂದಿಸಿ (SYN ಅನ್ನು ಈಗಾಗಲೇ ಹೊಂದಿಸಲಾಗಿದೆ):

const u32 cookie = 42;
tcp->ack_seq = bpf_htonl(bpf_ntohl(tcp->seq) + 1);
tcp->seq = bpf_htonl(cookie);
tcp->ack = 1;

TCP ಪೋರ್ಟ್‌ಗಳು, IP ವಿಳಾಸ ಮತ್ತು MAC ವಿಳಾಸಗಳನ್ನು ಬದಲಾಯಿಸಿ. XDP ಪ್ರೋಗ್ರಾಂನಿಂದ ಪ್ರಮಾಣಿತ ಗ್ರಂಥಾಲಯವನ್ನು ಪ್ರವೇಶಿಸಲಾಗುವುದಿಲ್ಲ, ಆದ್ದರಿಂದ memcpy() - ಕ್ಲಾಂಗ್ ಆಂತರಿಕತೆಯನ್ನು ಮರೆಮಾಡುವ ಮ್ಯಾಕ್ರೋ.

const u16 temp_port = tcp->source;
tcp->source = tcp->dest;
tcp->dest = temp_port;

const u32 temp_ip = ip->saddr;
ip->saddr = ip->daddr;
ip->daddr = temp_ip;

struct ethhdr temp_ether = *ether;
memcpy(ether->h_dest, temp_ether.h_source, ETH_ALEN);
memcpy(ether->h_source, temp_ether.h_dest, ETH_ALEN);

ಚೆಕ್ಸಮ್ಗಳ ಮರು ಲೆಕ್ಕಾಚಾರ

IPv4 ಮತ್ತು TCP ಚೆಕ್‌ಸಮ್‌ಗಳಿಗೆ ಹೆಡರ್‌ಗಳಲ್ಲಿ ಎಲ್ಲಾ 16-ಬಿಟ್ ಪದಗಳನ್ನು ಸೇರಿಸುವ ಅಗತ್ಯವಿರುತ್ತದೆ ಮತ್ತು ಹೆಡರ್‌ಗಳ ಗಾತ್ರವನ್ನು ಅವುಗಳಲ್ಲಿ ಬರೆಯಲಾಗುತ್ತದೆ, ಅಂದರೆ ಕಂಪೈಲ್ ಸಮಯದಲ್ಲಿ ತಿಳಿದಿಲ್ಲ. ಪರಿಶೀಲಕವು ಸಾಮಾನ್ಯ ಲೂಪ್ ಅನ್ನು ಬೌಂಡರಿ ವೇರಿಯಬಲ್‌ಗೆ ಬಿಟ್ಟುಬಿಡದ ಕಾರಣ ಇದು ಸಮಸ್ಯೆಯಾಗಿದೆ. ಆದರೆ ಹೆಡರ್‌ಗಳ ಗಾತ್ರವು ಸೀಮಿತವಾಗಿದೆ: ಪ್ರತಿಯೊಂದೂ 64 ಬೈಟ್‌ಗಳವರೆಗೆ. ನೀವು ನಿಗದಿತ ಸಂಖ್ಯೆಯ ಪುನರಾವರ್ತನೆಗಳೊಂದಿಗೆ ಲೂಪ್ ಮಾಡಬಹುದು, ಅದು ಮೊದಲೇ ಕೊನೆಗೊಳ್ಳಬಹುದು.

ಇದೆ ಎಂದು ನಾನು ಗಮನಿಸುತ್ತೇನೆ RFC 1624 ಪ್ಯಾಕೇಜ್‌ಗಳ ಸ್ಥಿರ ಪದಗಳನ್ನು ಮಾತ್ರ ಬದಲಾಯಿಸಿದರೆ ಚೆಕ್‌ಸಮ್ ಅನ್ನು ಭಾಗಶಃ ಮರು ಲೆಕ್ಕಾಚಾರ ಮಾಡುವುದು ಹೇಗೆ ಎಂಬುದರ ಕುರಿತು. ಆದಾಗ್ಯೂ, ವಿಧಾನವು ಸಾರ್ವತ್ರಿಕವಲ್ಲ, ಮತ್ತು ಅನುಷ್ಠಾನವನ್ನು ನಿರ್ವಹಿಸುವುದು ಹೆಚ್ಚು ಕಷ್ಟಕರವಾಗಿರುತ್ತದೆ.

ಚೆಕ್ಸಮ್ ಲೆಕ್ಕಾಚಾರ ಕಾರ್ಯ:

#define MAX_CSUM_WORDS 32
#define MAX_CSUM_BYTES (MAX_CSUM_WORDS * 2)

INTERNAL u32
sum16(const void* data, u32 size, const void* data_end) {
    u32 s = 0;
#pragma unroll
    for (u32 i = 0; i < MAX_CSUM_WORDS; i++) {
        if (2*i >= size) {
            return s; /* normal exit */
        }
        if (data + 2*i + 1 + 1 > data_end) {
            return 0; /* should be unreachable */
        }
        s += ((const u16*)data)[i];
    }
    return s;
}

ಆದರೂ size ಕರೆ ಮಾಡುವ ಕೋಡ್‌ನಿಂದ ಪರಿಶೀಲಿಸಲಾಗಿದೆ, ಎರಡನೇ ನಿರ್ಗಮನ ಸ್ಥಿತಿಯು ಅಗತ್ಯವಾಗಿರುತ್ತದೆ ಆದ್ದರಿಂದ ಪರಿಶೀಲಕನು ಲೂಪ್‌ನ ಪೂರ್ಣಗೊಳಿಸುವಿಕೆಯನ್ನು ಸಾಬೀತುಪಡಿಸಬಹುದು.

32-ಬಿಟ್ ಪದಗಳಿಗಾಗಿ, ಸರಳವಾದ ಆವೃತ್ತಿಯನ್ನು ಅಳವಡಿಸಲಾಗಿದೆ:

INTERNAL u32
sum16_32(u32 v) {
    return (v >> 16) + (v & 0xffff);
}

ವಾಸ್ತವವಾಗಿ ಚೆಕ್‌ಸಮ್‌ಗಳನ್ನು ಮರು ಲೆಕ್ಕಾಚಾರ ಮಾಡುವುದು ಮತ್ತು ಪ್ಯಾಕೆಟ್ ಅನ್ನು ಹಿಂದಕ್ಕೆ ಕಳುಹಿಸುವುದು:

ip->check = 0;
ip->check = carry(sum16(ip, ip_len, data_end));

u32 tcp_csum = 0;
tcp_csum += sum16_32(ip->saddr);
tcp_csum += sum16_32(ip->daddr);
tcp_csum += 0x0600;
tcp_csum += tcp_len << 8;
tcp->check = 0;
tcp_csum += sum16(tcp, tcp_len, data_end);
tcp->check = carry(tcp_csum);

return XDP_TX;

ಕಾರ್ಯ carry() RFC 32 ರ ಪ್ರಕಾರ 16-ಬಿಟ್ ಪದಗಳ 791-ಬಿಟ್ ಮೊತ್ತದಿಂದ ಚೆಕ್ಸಮ್ ಅನ್ನು ಮಾಡುತ್ತದೆ.

TCP ಹ್ಯಾಂಡ್ಶೇಕ್ ಪರಿಶೀಲನೆ

ಫಿಲ್ಟರ್ ಸರಿಯಾಗಿ ಸಂಪರ್ಕವನ್ನು ಸ್ಥಾಪಿಸುತ್ತದೆ netcat, ಅಂತಿಮ ACK ಅನ್ನು ಕಳೆದುಕೊಂಡಿದೆ, ಇದಕ್ಕೆ Linux RST ಪ್ಯಾಕೆಟ್‌ನೊಂದಿಗೆ ಪ್ರತಿಕ್ರಿಯಿಸಿತು, ಏಕೆಂದರೆ ನೆಟ್‌ವರ್ಕ್ ಸ್ಟಾಕ್ SYN ಅನ್ನು ಸ್ವೀಕರಿಸಲಿಲ್ಲ - ಅದನ್ನು SYNACK ಗೆ ಪರಿವರ್ತಿಸಲಾಗಿದೆ ಮತ್ತು ಹಿಂದಕ್ಕೆ ಕಳುಹಿಸಲಾಗಿದೆ - ಮತ್ತು OS ದೃಷ್ಟಿಕೋನದಿಂದ, ತೆರೆಯಲು ಸಂಬಂಧಿಸದ ಪ್ಯಾಕೆಟ್ ಬಂದಿತು ಸಂಪರ್ಕಗಳು.

$ sudo ip netns exec xdp-test   nc -nv 192.0.2.1 6666
192.0.2.1 6666: Connection reset by peer

ಪೂರ್ಣ ಪ್ರಮಾಣದ ಅಪ್ಲಿಕೇಶನ್‌ಗಳೊಂದಿಗೆ ಪರಿಶೀಲಿಸುವುದು ಮತ್ತು ಗಮನಿಸುವುದು ಮುಖ್ಯ tcpdump ಮೇಲೆ xdp-remote ಏಕೆಂದರೆ, ಉದಾಹರಣೆಗೆ, hping3 ತಪ್ಪಾದ ಚೆಕ್‌ಸಮ್‌ಗಳಿಗೆ ಪ್ರತಿಕ್ರಿಯಿಸುವುದಿಲ್ಲ.

XDP ದೃಷ್ಟಿಕೋನದಿಂದ, ಪರಿಶೀಲನೆಯು ಕ್ಷುಲ್ಲಕವಾಗಿದೆ. ಲೆಕ್ಕಾಚಾರದ ಅಲ್ಗಾರಿದಮ್ ಪ್ರಾಚೀನವಾಗಿದೆ ಮತ್ತು ಅತ್ಯಾಧುನಿಕ ಆಕ್ರಮಣಕಾರರಿಗೆ ದುರ್ಬಲವಾಗಿರುತ್ತದೆ. ಉದಾಹರಣೆಗೆ, Linux ಕರ್ನಲ್ ಕ್ರಿಪ್ಟೋಗ್ರಾಫಿಕ್ SipHash ಅನ್ನು ಬಳಸುತ್ತದೆ, ಆದರೆ XDP ಗಾಗಿ ಅದರ ಅನುಷ್ಠಾನವು ಈ ಲೇಖನದ ವ್ಯಾಪ್ತಿಯನ್ನು ಮೀರಿದೆ.

ಬಾಹ್ಯ ಸಂವಹನಕ್ಕೆ ಸಂಬಂಧಿಸಿದ ಹೊಸ TODO ಗಳಿಗಾಗಿ ಪರಿಚಯಿಸಲಾಗಿದೆ:

  • XDP ಪ್ರೋಗ್ರಾಂ ಅನ್ನು ಸಂಗ್ರಹಿಸಲಾಗುವುದಿಲ್ಲ cookie_seed (ಉಪ್ಪಿನ ರಹಸ್ಯ ಭಾಗ) ಜಾಗತಿಕ ವೇರಿಯಬಲ್‌ನಲ್ಲಿ, ನಿಮಗೆ ಕರ್ನಲ್‌ನಲ್ಲಿ ಸಂಗ್ರಹಣೆಯ ಅಗತ್ಯವಿದೆ, ಅದರ ಮೌಲ್ಯವನ್ನು ನಿಯತಕಾಲಿಕವಾಗಿ ವಿಶ್ವಾಸಾರ್ಹ ಜನರೇಟರ್‌ನಿಂದ ನವೀಕರಿಸಲಾಗುತ್ತದೆ.

  • ACK ಪ್ಯಾಕೆಟ್‌ನಲ್ಲಿ SYN ಕುಕೀ ಹೊಂದಾಣಿಕೆಯಾದರೆ, ನೀವು ಸಂದೇಶವನ್ನು ಮುದ್ರಿಸುವ ಅಗತ್ಯವಿಲ್ಲ, ಆದರೆ ಅದರಿಂದ ಪ್ಯಾಕೆಟ್‌ಗಳನ್ನು ರವಾನಿಸುವುದನ್ನು ಮುಂದುವರಿಸಲು ಪರಿಶೀಲಿಸಿದ ಕ್ಲೈಂಟ್‌ನ IP ಅನ್ನು ನೆನಪಿಡಿ.

ಕಾನೂನುಬದ್ಧ ಕ್ಲೈಂಟ್ ಪರಿಶೀಲನೆ:

$ sudoip netns exec xdp-test   nc -nv 192.0.2.1 6666
192.0.2.1 6666: Connection reset by peer

ಚೆಕ್ ಪಾಸ್ ಆಗಿದೆ ಎಂದು ಲಾಗ್‌ಗಳು ತೋರಿಸುತ್ತವೆ (flags=0x2 - ಇದು SYN, flags=0x10 ACK ಆಗಿದೆ):

Ether(proto=0x800)
  IP(src=0x20e6e11a dst=0x20e6e11e proto=6)
    TCP(sport=50836 dport=6666 flags=0x2)
Ether(proto=0x800)
  IP(src=0xfe2cb11a dst=0xfe2cb11e proto=6)
    TCP(sport=50836 dport=6666 flags=0x10)
      cookie matches for client 20200c0

ಪರಿಶೀಲಿಸಿದ IP ಗಳ ಯಾವುದೇ ಪಟ್ಟಿ ಇಲ್ಲದಿದ್ದರೂ, SYN ಪ್ರವಾಹದಿಂದ ಯಾವುದೇ ರಕ್ಷಣೆ ಇರುವುದಿಲ್ಲ, ಆದರೆ ಈ ಕೆಳಗಿನ ಆಜ್ಞೆಯಿಂದ ಪ್ರಾರಂಭಿಸಲಾದ ACK ಪ್ರವಾಹಕ್ಕೆ ಪ್ರತಿಕ್ರಿಯೆ ಇಲ್ಲಿದೆ:

sudo ip netns exec xdp-test   hping3 --flood -A -s 1111 -p 2222 192.0.2.1

ಲಾಗ್ ನಮೂದುಗಳು:

Ether(proto=0x800)
  IP(src=0x15bd11a dst=0x15bd11e proto=6)
    TCP(sport=3236 dport=2222 flags=0x10)
      cookie mismatch

ತೀರ್ಮಾನಕ್ಕೆ

ಕೆಲವೊಮ್ಮೆ ಸಾಮಾನ್ಯವಾಗಿ eBPF ಮತ್ತು ನಿರ್ದಿಷ್ಟವಾಗಿ XDP ಯನ್ನು ಅಭಿವೃದ್ಧಿ ವೇದಿಕೆಗಿಂತ ಮುಂದುವರಿದ ನಿರ್ವಾಹಕರ ಸಾಧನವಾಗಿ ಪ್ರಸ್ತುತಪಡಿಸಲಾಗುತ್ತದೆ. ವಾಸ್ತವವಾಗಿ, XDP ಎಂಬುದು ಕರ್ನಲ್‌ನಿಂದ ಪ್ಯಾಕೆಟ್‌ಗಳ ಪ್ರಕ್ರಿಯೆಗೆ ಅಡ್ಡಿಪಡಿಸುವ ಸಾಧನವಾಗಿದೆ ಮತ್ತು DPDK ಮತ್ತು ಇತರ ಕರ್ನಲ್ ಬೈಪಾಸ್ ಆಯ್ಕೆಗಳಂತಹ ಕರ್ನಲ್ ಸ್ಟಾಕ್‌ಗೆ ಪರ್ಯಾಯವಲ್ಲ. ಮತ್ತೊಂದೆಡೆ, XDP ನಿಮಗೆ ಸಾಕಷ್ಟು ಸಂಕೀರ್ಣವಾದ ತರ್ಕವನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸಲು ಅನುಮತಿಸುತ್ತದೆ, ಇದಲ್ಲದೆ, ಸಂಚಾರ ಪ್ರಕ್ರಿಯೆಯಲ್ಲಿ ಅಡಚಣೆಯಿಲ್ಲದೆ ನವೀಕರಿಸಲು ಸುಲಭವಾಗಿದೆ. ಪರಿಶೀಲಕವು ದೊಡ್ಡ ಸಮಸ್ಯೆಗಳನ್ನು ಸೃಷ್ಟಿಸುವುದಿಲ್ಲ; ವೈಯಕ್ತಿಕವಾಗಿ, ಬಳಕೆದಾರರ ಸ್ಪೇಸ್ ಕೋಡ್‌ನ ಭಾಗಗಳಿಗೆ ನಾನು ಇದನ್ನು ನಿರಾಕರಿಸುವುದಿಲ್ಲ.

ಎರಡನೇ ಭಾಗದಲ್ಲಿ, ವಿಷಯವು ಆಸಕ್ತಿದಾಯಕವಾಗಿದ್ದರೆ, ನಾವು ಪರಿಶೀಲಿಸಿದ ಕ್ಲೈಂಟ್‌ಗಳು ಮತ್ತು ಸಂಪರ್ಕ ಕಡಿತಗಳ ಕೋಷ್ಟಕವನ್ನು ಪೂರ್ಣಗೊಳಿಸುತ್ತೇವೆ, ಕೌಂಟರ್‌ಗಳನ್ನು ಕಾರ್ಯಗತಗೊಳಿಸುತ್ತೇವೆ ಮತ್ತು ಫಿಲ್ಟರ್ ಅನ್ನು ನಿರ್ವಹಿಸಲು ಬಳಕೆದಾರರ ಸ್ಥಳದ ಉಪಯುಕ್ತತೆಯನ್ನು ಬರೆಯುತ್ತೇವೆ.

ಉಲ್ಲೇಖಗಳು:

ಮೂಲ: www.habr.com

ಕಾಮೆಂಟ್ ಅನ್ನು ಸೇರಿಸಿ