SNMP ليس البروتوكول الأكثر سهولة في الاستخدام: ملفات MIB طويلة ومربكة للغاية ، ومن المستحيل تذكر OIDs. ولكن ماذا لو كنت بحاجة للعمل مع SNMP في Java؟ على سبيل المثال ، اكتب الاختبارات التلقائية للتحقق من واجهة برمجة تطبيقات خادم SNMP.
من خلال التجربة والخطأ ، مع كمية ضئيلة من المعلومات حول هذا الموضوع ، ما زلنا نفهم كيفية تكوين صداقات Java و SNMP.
في هذه السلسلة من المقالات سأحاول مشاركة الخبرة المكتسبة مع البروتوكول. سيتم تخصيص المقالة الأولى في السلسلة لتطبيق محلل ملفات MIB في Java. في الجزء الثاني سأتحدث عن كتابة عميل SNMP. في الجزء الثالث ، سنتحدث عن مثال حقيقي لاستخدام مكتبة مكتوبة: الاختبارات التلقائية للتحقق من التفاعل مع جهاز عبر SNMP.

الدخول
بدأ كل شيء بمهمة كتابة الاختبارات الذاتية للتحقق من تشغيل مسجل الصوت والفيديو عبر SNMP. ومما يزيد الموقف تعقيدًا أنه لا يوجد الكثير من المعلومات حول التفاعل مع SNMP في Java ، خاصة عندما يتعلق الأمر بقطاع اللغة الروسية على الإنترنت. بالطبع ، يمكن للمرء أن ينظر إلى C # أو Python. ولكن في C # ، فإن وضع البروتوكول معقد كما هو الحال في Java. هناك مكتبتان جيدتان في الثعبان ، ولكن لدينا بالفعل بنية تحتية جاهزة للاختبارات التلقائية REST API لهذا الجهاز في Java.
كما هو الحال مع أي بروتوكول اتصال شبكة آخر ، كنا بحاجة إلى عميل SNMP للعمل مع أنواع مختلفة من الطلبات. يجب أن تكون الاختبارات التلقائية قادرة على التحقق من نجاح طلبات GET و SET لمعلمات الحجم والجدول. بالنسبة للجداول ، كان مطلوبًا أيضًا أن تكون قادرًا على التحقق من إضافة السجلات وحذفها ، إذا كان الجدول نفسه يسمح بهذه العمليات.
ولكن بالإضافة إلى العميل ، كان يجب أن تحتوي المكتبة على فصل للعمل مع ملفات MIB. كان يجب أن تكون هذه الفئة قادرة على تحليل ملف MIB للحصول على أنواع البيانات وحدود القيم المقبولة وما إلى ذلك ، حتى لا يتم ترميز ما يمكن تغييره دائمًا.
أثناء البحث عن مكتبات مناسبة لـ Java ، لم نجد مكتبة واحدة تسمح بالعمل مع كل من الطلبات وملفات MIB. لذلك ، استقرنا في مكتبتين مختلفتين. بالنسبة للعميل ، كان اختيار org.snmp4j.snmp4j (https://www.snmp4j.org) المستخدم على نطاق واسع أمرًا منطقيًا تمامًا ، وبالنسبة لمحلل ملفات MIB ، تم الاختيار إلى مكتبة net.percederberg.mibble غير المعروفة جدًا (https: // www. mibble.org). إذا كان الاختيار مع snmp4j واضحًا ، فقد تم اختيار mibble لتوفر وثائق مفصلة بما فيه الكفاية (وإن كانت باللغة الإنجليزية) مع أمثلة. لذلك دعونا نبدأ.
كتابة محلل ملفات MIB
كل من شاهد ملفات MIB يعرف أن هذا مؤلم. دعنا نحاول إصلاح هذا بمساعدة محلل بسيط ، والذي سيسهل بشكل كبير البحث عن معلومات من ملف ، مما يقللها إلى استدعاء طريقة معينة.
يمكن استخدام هذا المحلل كأداة مساعدة منفصلة للعمل مع ملفات MIB أو تضمينها في أي مشروع آخر ضمن SNMP ، على سبيل المثال ، عند كتابة عميل SNMP أو اختبار الأتمتة.
إعداد المشروع
لسهولة التجميع ، نستخدم Maven. اعتمادًا على إضافة مكتبة net.percederberg.mibble (https://www.mibble.org) ، والتي ستسهل علينا العمل مع ملفات MIB:
<dependency> <groupId>net.percederberg.mibble</groupId> <artifactId>mibble</artifactId> <version>2.9.3</version> </dependency>
نظرًا لأنه لا يكمن في مستودع Maven المركزي ، فإننا نضيف الكود التالي إلى pom.xml:
<repositories> <repository> <id>opennms</id> <name>OpenNMS</name> <url>http://repo.opennms.org/maven2/</url> </repository> </repositories>
إذا تم تجميع المشروع باستخدام Maven بدون أخطاء ، فإن كل شيء جاهز للعمل. يبقى فقط لإنشاء فئة محلل (دعنا نسميها MIBParser) واستيراد كل ما نحتاجه ، وهي:
import net.percederberg.mibble.*;
قم بتنزيل ملف MIB والتحقق من صحته
داخل الفصل سيكون هناك حقل واحد فقط - كائن من النوع net.percederberg.mibble.Mib لتخزين ملف MIB الذي تم تنزيله:
private Mib mib;
لتحميل ملف ، نكتب هذه الطريقة:
private Mib loadMib(File file) throws MibLoaderException, IOException { MibLoader loader = new MibLoader(); Mib mib; file = file.getAbsoluteFile(); try { loader.addDir(file.getParentFile()); mib = loader.load(file); } catch (MibLoaderException e) { e.getLog().printTo(System.err); throw e; } catch (IOException e) { e.printStackTrace(); throw e; } return mib; }
تتحقق الفئة net.percederberg.mibble.MIBLoader من الملف الذي نحاول تحميله وتلقي net.percederberg.mibble.MibLoaderException إذا وجد أي أخطاء فيه ، بما في ذلك أخطاء الاستيراد من ملفات MIB الأخرى ، إذا كانت لا تكمن في نفس الدليل أو لا تحتوي على أحرف MIB المستوردة.
في طريقة loadMib ، نلتقط جميع الاستثناءات ، ونكتب عنها في السجل ونعيد توجيهها ، لأن في هذه المرحلة ، من المستحيل استمرار العمل - الملف غير صالح.
نسمي الطريقة المكتوبة في مُنشئ المحلل اللغوي:
public MIBParser(File file) throws MibLoaderException, IOException { if (!file.exists()) throw new FileNotFoundException("File not found in location: " + file.getAbsolutePath()); mib = loadMib(file.getAbsoluteFile()); if (!mib.isLoaded()) throw new MibLoaderException(file, "Not loaded."); }
إذا تم تنزيل الملف وتحليله بنجاح ، فتابع العمل.
طرق استرجاع المعلومات من ملف MIB
باستخدام طرق فئة net.percederberg.mibble.Mib ، يمكنك البحث عن أحرف فردية لملف MIB بالاسم أو OID عن طريق استدعاء أساليب getSymbol (اسم السلسلة) أو getSymbolByOid (String oid) ، على التوالي. تُرجع هذه الأساليب كائن net.percederberg.mibble.MibSymbol ، الذي سنستخدمه للحصول على المعلومات الضرورية حول رمز MIB محدد.
لنبدأ بأبسط الطرق والكتابة للحصول على اسم الرمز من خلال OID الخاص به ، وعلى العكس من ذلك ، OID بالاسم:
public String getName(String oid) { return mib.getSymbolByOid(oid).getName(); } public String getOid(String name) { String oid = null; MibSymbol s = mib.getSymbol(name); if (s instanceof MibValueSymbol) { oid = ((MibValueSymbol) s).getValue().toString(); if (((MibValueSymbol) s).isScalar()) oid = new OID(oid).append(0).toDottedString(); } return oid; }
ربما هذه هي ميزات ملف MIB معين ، والذي كنت بحاجة للعمل معه ، ولكن لسبب ما ، أعادت المعلمات العددية OID بدون صفر في النهاية ، لذلك تمت إضافة رمز إلى طريقة الحصول على OID ، والتي ، إذا كان MIB الحرف هو عددي ، يضيف ببساطة ".0" إلى OID المستلم باستخدام طريقة الإلحاق (فهرس داخلي) من فئة net.percederberg.mibble.OID. إذا كان يعمل لك دون عكاز ، تهانينا :)
للحصول على بقية البيانات الموجودة على الرمز ، نكتب طريقة مساعدة واحدة ، حيث نحصل على الكائن net.percederberg.mibble.snmp.SnmpObjectType ، والذي يحتوي على جميع المعلومات الضرورية حول رمز MIB الذي تم الحصول عليه منه.
private SnmpObjectType getSnmpObjectType(MibSymbol symbol) { if (symbol instanceof MibValueSymbol) { MibType type = ((MibValueSymbol) symbol).getType(); if (type instanceof SnmpObjectType) { return (SnmpObjectType) type; } } return null; }
على سبيل المثال ، يمكننا الحصول على نوع رمز MIB:
public String getType(String name) { MibSymbol s = mib.getSymbol(name); if (getSnmpObjectType(s).getSyntax().getReferenceSymbol() == null) return getSnmpObjectType(s).getSyntax().getName(); else return getSnmpObjectType(s).getSyntax().getReferenceSymbol().getName(); }
هناك طريقتان للحصول على الكتابة ، لأن بالنسبة للأنواع البدائية ، يعمل الخيار الأول:
getSnmpObjectType(s).getSyntax().getName();
وبالنسبة للمستوردات الثانية:
getSnmpObjectType(s).getSyntax().getReferenceSymbol().getName();
يمكنك الحصول على مستوى وصول حرف:
public String getAccess(String name) { MibSymbol s = mib.getSymbol(name); return getSnmpObjectType(s).getAccess().toString(); }
الحد الأدنى للقيمة الصالحة لمعلمة رقمية:
public Integer getDigitMinValue(String name) { MibSymbol s = mib.getSymbol(name); String syntax = getSnmpObjectType(s).getSyntax().toString(); if (syntax.contains("STRING")) return null; Pattern p = Pattern.compile("(-?\\d+)..(-?\\d+)"); Matcher m = p.matcher(syntax); if (m.find()) { return Integer.parseInt(m.group(1)); } return null; }
القيمة القصوى المسموح بها لمعلمة رقمية:
public Integer getDigitMaxValue(String name) { MibSymbol s = mib.getSymbol(name); String syntax = getSnmpObjectType(s).getSyntax().toString(); if (syntax.contains("STRING")) return null; Pattern p = Pattern.compile("(-?\\d+)..(-?\\d+)"); Matcher m = p.matcher(syntax); if (m.find()) { return Integer.parseInt(m.group(2)); } return null; }
الحد الأدنى المسموح به لطول السلسلة (يعتمد على نوع السلسلة):
public Integer getStringMinLength(String name) { MibSymbol s = this.mib.getSymbol(name); String syntax = this.getSnmpObjectType(s).getSyntax().toString(); Pattern p = Pattern.compile("(-?\\d+)..(-?\\d+)"); Matcher m = p.matcher(syntax); return syntax.contains("STRING") && m.find()?Integer.valueOf(Integer.parseInt(m.group(1))):null; }
الحد الأقصى المسموح به لطول السلسلة (يعتمد على نوع السلسلة):
public Integer getStringMaxLength(String name) { MibSymbol s = mib.getSymbol(name); String syntax = getSnmpObjectType(s).getSyntax().toString(); Pattern p = Pattern.compile("(-?\\d+)..(-?\\d+)"); Matcher m = p.matcher(syntax); if (syntax.contains("STRING") && m.find()) { return Integer.parseInt(m.group(2)); } return null; }
يمكنك أيضًا الحصول على أسماء جميع الأعمدة في الجدول حسب اسمه:
public ArrayList<String> getTableColumnNames(String tableName) { ArrayList<String> mibSymbolNamesList = new ArrayList<>(); MibValueSymbol table = (MibValueSymbol) mib.findSymbol(tableName, true); if (table.isTable() && table.getChild(0).isTableRow()) { MibValueSymbol[] symbols = table.getChild(0).getChildren(); for (MibValueSymbol mvs : symbols) { mibSymbolNamesList.add(mvs.getName()); } } return mibSymbolNamesList; }
أولاً ، بطريقة قياسية ، نحصل على رمز MIB بالاسم:
MibValueSymbol table = (MibValueSymbol) mib.findSymbol(tableName, true);
ثم نتحقق مما إذا كان جدولًا وأن رمز MIB التابع هو صف من هذا الجدول ، وإذا كانت الحالة صحيحة ، فإننا في الحلقة ننتقل عبر توابع صف الجدول ونضيف اسم العنصر إلى الصفيف الناتج:
if (table.isTable() && table.getChild(0).isTableRow()) { MibValueSymbol[] symbols = table.getChild(0).getChildren(); for (MibValueSymbol mvs : symbols) { mibSymbolNamesList.add(mvs.getName()); } }
الملخص
هذه الأساليب كافية للحصول على أي معلومات من ملف MIB لكل حرف معين ، مع معرفة اسمه فقط. على سبيل المثال ، عند كتابة عميل SNMP ، يمكنك تضمين هذا المحلل اللغوي فيه بحيث لا تقبل طرق العميل OIDs ، ولكن أسماء رموز MIB. سيؤدي ذلك إلى زيادة موثوقية التعليمات البرمجية ، مثل قد لا يؤدي خطأ مطبعي في OID إلى الحرف الذي نريد الإشارة إليه. ولا معرفات شخصية - لا توجد مشاكل.
زائد هو قراءة الرمز ، مما يعني قابلية صيانته. من الأسهل فهم جوهر المشروع إذا كانت الشفرة تعمل بأسماء بشرية.
تطبيق آخر هو أتمتة الاختبار. في بيانات الاختبار ، يمكن للمرء الحصول على القيم الحدية للمعلمات العددية ديناميكيًا من ملف MIB. لذلك ، إذا تغيرت القيم الحدية لبعض رموز MIB في الإصدار الجديد من المكون المختبر ، فلن تضطر إلى تغيير رمز الاختبار التلقائي.
بشكل عام ، استخدام المحلل اللغوي ، يصبح العمل مع ملفات MIB أكثر متعة ، وتتوقف عن أن تكون مؤلمة.