最近是被這個snmp的trap給坑了,其實回想起來主要是對這個不了解。特別是對snmp協議研究不夠深入,
真的不想看這些協議東西,只想大概知道。結果在開發snmp trap時候被坑了,下面列下自己踩到的坑:
1、snmp的trap的中文問題
本來在自己的機器上運行挺好,但是測試說發現亂碼,內心直呼不可能吧,結果還真是,原來的代碼如下:
if (val_type.equals("OCTET STRING")){ //字符串類型轉碼,防止里面有中文內容 strValue = new String(((OctetString)recVB.getVariable()).getValue(), charsetCode); } else { strValue = new String (recVB.getVariable().toString()); }
charsetCode 為配置的消息編碼類型, 這里說明下java的String都是Unicode編碼的,
說明下:
如果想獲得這個字符串的byte[] 可以通過類似:String.getBytes("UTF-8")來獲得。
如果這樣String.getBytes(); 就會依賴於JVM的字符集編碼,WINDOWS下一般為GBK。
(要想改變JVM缺省字符集編碼, 啟動JVM時用選項-Dfile.encodeing=UTF-8)
注意千萬不要在程序里面設置沒有用的如下設置:
System.getProperties().setProperty("file.encoding", "GBK");
這樣來解決默認編碼的問題是不可行的!!!不可行的!!!不可行的!!!
getBytes() ---->StringCoding.encode()--> String csn = Charset.defaultCharset().name();
/** * Returns the default charset of this Java virtual machine. * * <p> The default charset is determined during virtual-machine startup and * typically depends upon the locale and charset of the underlying * operating system. * * @return A charset object for the default charset * * @since 1.5 */ public static Charset defaultCharset() { if (defaultCharset == null) { synchronized (Charset.class) { String csn = AccessController.doPrivileged( new GetPropertyAction("file.encoding")); Charset cs = lookup(csn); if (cs != null) defaultCharset = cs; else defaultCharset = forName("UTF-8"); } } return defaultCharset; }
看到了吧,這個是個靜態的方法,只要第一次運行defaultCharset 這個不為空了之后,后面就和file.encoding無關了,所以你基本上你無法保證
你在第一次調用之前設置,比如java其他類庫會不會已經調用了getBytes(),只要一旦調用編碼就固定了。
這個問題導致我在客戶端測試的時候,配置的編碼和實際發送的編碼不一致,后來自己知道可以通過-Dfile.encodeing=UTF-8選項運行了。
有個簡單的辦法,可以把getBytes的內容打印出來就可以大概知道漢字是什么編碼的:
System.out.println("bytes:"+Arrays.toString(strTmp.getBytes()));
GBK都是2個字節的,而UTF-8一般是2個或三個字節表示一個漢字。
2、配置文件里面項目大小寫
flume的配置文件,在讀取的時候是區分大小寫的,所以這個不要寫錯了,或者在程序中忽視,自己竟然被坑到了,下次對配置還是忽略大小寫的好。
3、Trap 的V3 版本會丟棄包問題
開發同事在測試中,說V3的Trap消息運行一會會丟包,嚴格來說不是丟包,是說運行一段時間后,V1、V2版本的消息正常接收,SNMP Trap的V3
版本的消息無法接收到,真坑,看了下Snmp4J,找不到在哪里把日志啟動起來,╮(╯▽╰)╭,在初始化的地方用:
org.snmp4j.log.LogFactory.setLogFactory(new ConsoleLogFactory()); 來進行初始化下,結果在不接受V3的Trap包的時候,會打印出來:
1.3.6.1.6.3.15.1.1.2.0=0這條莫名其妙的記錄,有記錄就好,然后我順着這條線索查下去,了解的SNMP的時間窗口,這個對應的含義是:
IdNotInTimeWindow 后來繼續查SNMP4J的源碼,發現在USM中有這樣一段相關代碼:
if (securityLevel >= 2) { if (statusInfo != null) { int authParamsPos = usmSecurityParameters.getAuthParametersPosition() + usmSecurityParameters.getSecurityParametersPosition(); boolean authentic = auth.isAuthentic(user.getAuthenticationKey(), message, 0, message.length, new ByteArrayWindow(message, authParamsPos, 12)); if (!(authentic)) { if (logger.isDebugEnabled()) { logger.debug("RFC3414 §3.2.6 Wrong digest -> authentication failure: " + usmSecurityParameters.getAuthenticationParameters().toHexString()); } CounterEvent event = new CounterEvent(this, SnmpConstants.usmStatsWrongDigests); fireIncrementCounter(event); statusInfo.setSecurityLevel(new Integer32(1)); statusInfo.setErrorIndication(new VariableBinding(event.getOid(), event.getCurrentValue())); return 1408; } usmSecurityStateReference.setAuthenticationKey(user.getAuthenticationKey()); usmSecurityStateReference.setPrivacyKey(user.getPrivacyKey()); usmSecurityStateReference.setAuthenticationProtocol(auth); usmSecurityStateReference.setPrivacyProtocol(priv); int status = this.timeTable.checkTime( new UsmTimeEntry(securityEngineID, usmSecurityParameters.getAuthoritativeEngineBoots(), usmSecurityParameters.getAuthoritativeEngineTime())); switch (status) { case 1411: logger.debug("RFC3414 §3.2.7.a Not in time window; engineID='" + securityEngineID + "', engineBoots=" + usmSecurityParameters.getAuthoritativeEngineBoots() + ", engineTime=" + usmSecurityParameters.getAuthoritativeEngineTime()); CounterEvent event = new CounterEvent(this, SnmpConstants.usmStatsNotInTimeWindows); fireIncrementCounter(event); statusInfo.setSecurityLevel(new Integer32(2)); statusInfo.setErrorIndication(new VariableBinding(event.getOid(), event.getCurrentValue())); return status; case 1410: if (logger.isDebugEnabled()) { logger.debug("RFC3414 §3.2.7.b - Unkown engine ID: " + securityEngineID); } CounterEvent event = new CounterEvent(this, SnmpConstants.usmStatsNotInTimeWindows); fireIncrementCounter(event); statusInfo.setSecurityLevel(new Integer32(2)); statusInfo.setErrorIndication(new VariableBinding(event.getOid(), event.getCurrentValue())); return status; } }
重點在於: int status = this.timeTable.checkTime(
new UsmTimeEntry(securityEngineID, usmSecurityParameters.getAuthoritativeEngineBoots(), usmSecurityParameters.getAuthoritativeEngineTime()));
通過這句話檢查是否在時間窗口內,如果不在時間窗口內直接就拋出去。
這句話又調用了其他的方法,來讓我們看下:在UsmTimeTable.java里面
public synchronized int checkTime(UsmTimeEntry entry) { int now = (int) (System.currentTimeMillis() / 1000L); if (this.localTime.getEngineID().equals(entry.getEngineID())) { if ((this.localTime.getEngineBoots() == 2147483647) || (this.localTime.getEngineBoots() != entry.getEngineBoots()) || (Math.abs(now + this.localTime.getTimeDiff() - entry.getLatestReceivedTime()) > 150)) { if (logger.isDebugEnabled()) { logger.debug( "CheckTime: received message outside time window (authorative):" + ((this.localTime.getEngineBoots() != entry.getEngineBoots()) ? "engineBoots differ" : new StringBuffer().append("") .append(Math.abs(now + this.localTime.getTimeDiff() - entry.getLatestReceivedTime())) .append(" > 150").toString())); } return 1411; } if (logger.isDebugEnabled()) { logger.debug("CheckTime: time ok (authorative)"); } return 0; }
這個函數就是檢查時間窗函數,注意了,getEngineBoots獲取的是引擎運行次數,在第一次收到消息后,每秒增加一次,getEngineID標示引擎ID,好像一個用戶一個引擎。
然后判斷下這個時間:getLastestReceivedTime()注意這個時間是Snmp的Report消息的時候匯報時間,如果從開始收到第一條消息到150s內還沒有匯報,則認為是不在時間窗口的丟棄掉。
嘗試通過: snmpListener.getUSM().getTimeTable().getEntry(new OctetString(securityName)).setLatestReceivedTime(((int) (System.currentTimeMillis() / 1000L))); 這個方法來設置下,結果還一樣,可能還有更好辦法。
不過既然是協議的要求,這種防止攻擊的機制,那么就暫時保留吧。