diff options
Diffstat (limited to 'lib/snmp/src/misc/snmp_log.erl')
-rw-r--r-- | lib/snmp/src/misc/snmp_log.erl | 584 |
1 files changed, 584 insertions, 0 deletions
diff --git a/lib/snmp/src/misc/snmp_log.erl b/lib/snmp/src/misc/snmp_log.erl new file mode 100644 index 0000000000..c3932ccc08 --- /dev/null +++ b/lib/snmp/src/misc/snmp_log.erl @@ -0,0 +1,584 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(snmp_log). + + +-export([ + create/4, create/5, + change_size/2, close/1, sync/1, info/1, + log/4, + log_to_txt/5, log_to_txt/6, log_to_txt/7, + log_to_io/4, log_to_io/5, log_to_io/6 + ]). + + +-define(SNMP_USE_V3, true). +-include("snmp_types.hrl"). + +-define(VMODULE,"LOG"). +-include("snmp_verbosity.hrl"). + +-define(LOG_FORMAT, internal). +-define(LOG_TYPE, wrap). + + +%% -------------------------------------------------------------------- +%% Exported functions +%% -------------------------------------------------------------------- + + +%% -- create --- + +create(Name, File, Size, Repair) -> + create(Name, File, Size, Repair, false). + +create(Name, File, Size, Repair, Notify) -> + ?vtrace("create -> entry with" + "~n Name: ~p" + "~n File: ~p" + "~n Size: ~p" + "~n Repair: ~p" + "~n Notify: ~p", [Name, File, Size, Repair, Notify]), + log_open(Name, File, Size, Repair, Notify). + + +%% -- close --- + +close(Log) -> + ?vtrace("close -> entry with" + "~n Log: ~p", [Log]), + disk_log:close(Log). + + +%% -- close --- + +sync(Log) -> + ?vtrace("sync -> entry with" + "~n Log: ~p", [Log]), + disk_log:sync(Log). + +%% -- info --- + +info(Log) -> + case disk_log:info(Log) of + Info when is_list(Info) -> + Items = [no_current_bytes, no_current_items, + current_file, no_overflows], + info_filter(Items, Info, []); + Else -> + Else + end. + +info_filter([], _Info, Acc) -> + {ok, Acc}; +info_filter([Item|Items], Info, Acc) -> + case lists:keysearch(Item, 1, Info) of + {value, New} -> + info_filter(Items, Info, [New|Acc]); + false -> + info_filter(Items, Info, Acc) + end. + + +%% -- log --- + +%%----------------------------------------------------------------- +%% For efficiency reasons, we want to log the packet as a binary. +%% This is only possible for messages that are not encrypted. +%% Therefore, Packet can be either a binary (encoded message), or +%% a tuple {V3Hdr, ScopedPduBytes} +%% +%% log(Log, Packet, Addr, Port) +%%----------------------------------------------------------------- + + +log(Log, Packet, Addr, Port) -> + ?vtrace("log -> entry with" + "~n Log: ~p" + "~n Addr: ~p" + "~n Port: ~p", [Log, Addr, Port]), + Entry = {timestamp(), Packet, Addr, Port}, + disk_log:alog(Log, Entry). + + + +%% -- change_size --- + +change_size(Log, NewSize) -> + ?vtrace("change_size -> entry with" + "~n Log: ~p" + "~n NewSize: ~p", [Log, NewSize]), + disk_log:change_size(Log, NewSize). + + +%% -- log_to_txt --- + +log_to_txt(Log, FileName, Dir, Mibs, TextFile) -> + log_to_txt(Log, FileName, Dir, Mibs, TextFile, null, null). + +log_to_txt(Log, FileName, Dir, Mibs, TextFile, Start) -> + log_to_txt(Log, FileName, Dir, Mibs, TextFile, Start, null). + +log_to_txt(Log, FileName, Dir, Mibs, TextFile, Start, Stop) + when is_list(Mibs) andalso is_list(TextFile) -> + ?vtrace("log_to_txt -> entry with" + "~n Log: ~p" + "~n FileName: ~p" + "~n Dir: ~p" + "~n Mibs: ~p" + "~n TextFile: ~p" + "~n Start: ~p" + "~n Stop: ~p", + [Log, FileName, Dir, Mibs, TextFile, Start, Stop]), + File = filename:join(Dir, FileName), + Converter = fun(L) -> + do_log_to_file(L, TextFile, Mibs, Start, Stop) + end, + log_convert(Log, File, Converter). + + +%% -- log_to_io --- + +log_to_io(Log, FileName, Dir, Mibs) -> + log_to_io(Log, FileName, Dir, Mibs, null, null). + +log_to_io(Log, FileName, Dir, Mibs, Start) -> + log_to_io(Log, FileName, Dir, Mibs, Start, null). + +log_to_io(Log, FileName, Dir, Mibs, Start, Stop) + when is_list(Mibs) -> + File = filename:join(Dir, FileName), + Converter = fun(L) -> + do_log_to_io(L, Mibs, Start, Stop) + end, + log_convert(Log, File, Converter). + + +%% -------------------------------------------------------------------- +%% Internal functions +%% -------------------------------------------------------------------- + + +%% -- log_convert --- + +log_convert(Log, File, Converter) -> + %% First check if the caller process has already opened the + %% log, because if we close an already open log we will cause + %% a runtime error. + case is_owner(Log) of + true -> + Converter(Log); + false -> + %% Not yet member of the ruling party, apply for membership... + %% If a log is opened as read_write it is not possible to + %% open it as read_only. So, to get around this we open + %% it under a different name... + Log2 = convert_name(Log), + case log_open(Log2, File) of + {ok, _} -> + Res = Converter(Log2), + disk_log:close(Log2), + Res; + {error, {name_already_open, _}} -> + Converter(Log2); + {error, Reason} -> + {error, {Log, Reason}} + end + end. + +convert_name(Name) when is_list(Name) -> + Name ++ "_tmp"; +convert_name(Name) when is_atom(Name) -> + list_to_atom(atom_to_list(Name) ++ "_tmp"); +convert_name(Name) -> + lists:flatten(io_lib:format("~w_tmp", [Name])). + + +%% -- do_log_to_text --- + +do_log_to_file(Log, TextFile, Mibs, Start, Stop) -> + case file:open(TextFile, [write]) of + {ok, Fd} -> + MiniMib = snmp_mini_mib:create(Mibs), + Write = fun(X) -> + case format_msg(X, MiniMib, Start, Stop) of + {ok, S} -> + io:format(Fd, "~s", [S]); + _ -> + ok + end + end, + Res = (catch loop(disk_log:chunk(Log, start), Log, Write)), + snmp_mini_mib:delete(MiniMib), + file:close(Fd), + Res; + {error, Reason} -> + {error, {TextFile, Reason}} + end. + + +do_log_to_io(Log, Mibs, Start, Stop) -> + MiniMib = snmp_mini_mib:create(Mibs), + Write = fun(X) -> + case format_msg(X, MiniMib, Start, Stop) of + {ok, S} -> + io:format("~s", [S]); + _ -> + ok + end + end, + (catch loop(disk_log:chunk(Log, start), Log, Write)), + snmp_mini_mib:delete(MiniMib), + ok. + + +loop(eof, _Log, _Write) -> + ok; +loop({error, _} = Error, _Log, _Write) -> + Error; +loop({corrupt_log_file, _} = Reason, _Log, _Write) -> + {error, Reason}; +loop({Cont, Terms}, Log, Write) -> + case (catch lists:foreach(Write, Terms)) of + {'EXIT', Reason} -> + {error, Reason}; + _ -> + loop(disk_log:chunk(Log, Cont), Log, Write) + end; +loop({Cont, Terms, BadBytes}, Log, Write) -> + error_logger:error_msg("Skipping ~w bytes while converting ~p~n~n", + [BadBytes, Log]), + case (catch lists:foreach(Write, Terms)) of + {'EXIT', Reason} -> + {error, Reason}; + _ -> + loop(disk_log:chunk(Log, Cont), Log, Write) + end; +loop(Error, _Log, _Write) -> + Error. + +format_msg({TimeStamp, {V3Hdr, ScopedPdu}, {Addr, Port}}, + Mib, Start, Stop) -> + format_msg({TimeStamp, {V3Hdr, ScopedPdu}, Addr, Port}, + Mib, Start, Stop); +format_msg({TimeStamp, {V3Hdr, ScopedPdu}, Addr, Port}, + Mib, Start, Stop) -> +% io:format("format_msg -> entry with" +% "~n TimeStamp: ~p" +% "~n Start: ~p" +% "~n Stop: ~p", [TimeStamp, Start, Stop]), + case timestamp_filter(TimeStamp, Start, Stop) of + true -> + case (catch snmp_pdus:dec_scoped_pdu(ScopedPdu)) of + ScopedPDU when is_record(ScopedPDU, scopedPdu) -> + Msg = #message{version = 'version-3', + vsn_hdr = V3Hdr, + data = ScopedPDU}, + f(ts2str(TimeStamp), Msg, Addr, Port, Mib); + {'EXIT', Reason} -> + format_tab("** error in log file at ~s from ~p:~w ~p\n\n", + [ts2str(TimeStamp), ip(Addr), Port, Reason]) + end; + false -> + ignore + end; +format_msg({TimeStamp, Packet, {Addr, Port}}, Mib, Start, Stop) -> + format_msg({TimeStamp, Packet, Addr, Port}, Mib, Start, Stop); +format_msg({TimeStamp, Packet, Addr, Port}, Mib, Start, Stop) -> + case timestamp_filter(TimeStamp, Start, Stop) of + true -> + case (catch snmp_pdus:dec_message(binary_to_list(Packet))) of + Msg when is_record(Msg, message) -> + f(ts2str(TimeStamp), Msg, Addr, Port, Mib); + {'EXIT', Reason} -> + format_tab("** error in log file ~p\n\n", [Reason]) + end; + false -> + ignore + end; +format_msg(_, _Mib, _Start, _Stop) -> + format_tab("** unknown entry in log file\n\n", []). + +f(TimeStamp, #message{version = Vsn, vsn_hdr = VsnHdr, data = Data}, + Addr, Port, Mib) -> + Str = format_pdu(Data, Mib), + HdrStr = format_header(Vsn, VsnHdr), + case get_type(Data) of + trappdu -> + f_trap(TimeStamp, Vsn, HdrStr, Str, Addr, Port); + 'snmpv2-trap' -> + f_trap(TimeStamp, Vsn, HdrStr, Str, Addr, Port); + 'inform-request' -> + f_inform(TimeStamp, Vsn, HdrStr, Str, Addr, Port); + 'get-response' -> + f_response(TimeStamp, Vsn, HdrStr, Str, Addr, Port); + report -> + f_report(TimeStamp, Vsn, HdrStr, Str, Addr, Port); + _ -> + f_request(TimeStamp, Vsn, HdrStr, Str, Addr, Port) + end. + +f_request(TimeStamp, Vsn, HdrStr, Str, Addr, Port) -> + format_tab("request ~s:~w - ~s [~s] ~w\n~s", + [ip(Addr), Port, HdrStr, TimeStamp, Vsn, Str]). + +f_response(TimeStamp, Vsn, HdrStr, Str, Addr, Port) -> + format_tab("response ~s:~w - ~s [~s] ~w\n~s", + [ip(Addr), Port, HdrStr, TimeStamp, Vsn, Str]). + +f_report(TimeStamp, Vsn, HdrStr, Str, Addr, Port) -> + format_tab("report ~s:~w - ~s [~s] ~w\n~s", + [ip(Addr), Port, HdrStr, TimeStamp, Vsn, Str]). + +f_trap(TimeStamp, Vsn, HdrStr, Str, Addr, Port) -> + format_tab("trap ~s:~w - ~s [~s] ~w\n~s", + [ip(Addr), Port, HdrStr, TimeStamp, Vsn, Str]). + +f_inform(TimeStamp, Vsn, HdrStr, Str, Addr, Port) -> + format_tab("inform ~s:~w - ~s [~s] ~w\n~s", + [ip(Addr), Port, HdrStr, TimeStamp, Vsn, Str]). + + +%% Convert a timestamp 2-tupple to a printable string +%% +ts2str({Local,Universal}) -> + dat2str(Local) ++ " , " ++ dat2str(Universal); +ts2str(_) -> + "". + +%% Convert a datetime 2-tupple to a printable string +%% +dat2str({{Y,M,D},{H,Min,S}}) -> + io_lib:format("~w-~w-~w,~w:~w:~w",[Y,M,D,H,Min,S]). + + +timestamp_filter({Local,Universal},Start,Stop) -> + tsf_ge(Local,Universal,Start) and tsf_le(Local,Universal,Stop); +timestamp_filter(_,_Start,_Stop) -> + true. + +tsf_ge(_Local,_Universal,null) -> + true; +tsf_ge(Local,_Universal,{local_time,DateTime}) -> + tsf_ge(Local,DateTime); +tsf_ge(_Local,Universal,{universal_time,DateTime}) -> + tsf_ge(Universal,DateTime); +tsf_ge(Local,_Universal,DateTime) -> + tsf_ge(Local,DateTime). + +tsf_ge(TimeStamp,DateTime) -> + T1 = calendar:datetime_to_gregorian_seconds(TimeStamp), + T2 = calendar:datetime_to_gregorian_seconds(DateTime), + T1 >= T2. + +tsf_le(_Local,_Universal,null) -> + true; +tsf_le(Local,_Universal,{local_time,DateTime}) -> + tsf_le(Local,DateTime); +tsf_le(_Local,Universal,{universal_time,DateTime}) -> + tsf_le(Universal,DateTime); +tsf_le(Local,_Universal,DateTime) -> + tsf_le(Local,DateTime). + +tsf_le(TimeStamp,DateTime) -> + T1 = calendar:datetime_to_gregorian_seconds(TimeStamp), + T2 = calendar:datetime_to_gregorian_seconds(DateTime), + T1 =< T2. + + +%% In the output replace TAB by ESC TAB, and add a single trailing TAB. +%% +format_tab(Format, Args) -> + Str = lists:flatten(io_lib:format(Format, Args)), + DStr = lists:map(fun($\t) -> "\e\t"; (C) -> C end, Str), + {ok, io_lib:format("~s\t", [DStr])}. + + +format_header('version-1', CommunityStr) -> + CommunityStr; +format_header('version-2', CommunityStr) -> + CommunityStr; +format_header('version-3', #v3_hdr{msgFlags = MsgFlags, + msgSecurityModel = SecModel, + msgSecurityParameters = SecParams}) -> + SecLevel = snmp_misc:get_sec_level(MsgFlags), + case SecModel of + ?SEC_USM -> + case catch snmp_pdus:dec_usm_security_parameters(SecParams) of + #usmSecurityParameters{msgAuthoritativeEngineID = AuthEngineID, + msgUserName = UserName} -> + io_lib:format("~w:\"~s\":\"~s\"", + [SecLevel, AuthEngineID, UserName]); + _ -> + "-" + end; + _ -> + "\"unknown security model\"" + end. + + +format_pdu(#scopedPdu{contextName = Context, data = Pdu}, Mib) -> + io_lib:format("Context: \"~s\"\n~s", + [Context, snmp_misc:format_pdu(Pdu, Mib)]); +format_pdu(Pdu, Mib) -> + snmp_misc:format_pdu(Pdu, Mib). + +get_type(#scopedPdu{data = Pdu}) -> + get_type(Pdu); +get_type(Pdu) when is_record(Pdu, trappdu) -> + trappdu; +get_type(#pdu{type = Type}) -> + Type. + + +ip({A,B,C,D}) -> + io_lib:format("~w.~w.~w.~w", [A,B,C,D]). + + + +%% ------------------------------------------------------------------- +%% Various utility functions +%% ------------------------------------------------------------------- + +log_open(Name, File, Size, Repair, Notify) -> + case do_log_open(Name, File, Size, Repair, Notify) of + {ok, Log} -> + {ok, Log}; + {repaired, Log, Rec, Bad} -> + ?vlog("log_open -> repaired: " + "~n Rec: ~p" + "~n Bad: ~p", [Rec, Bad]), + {ok, Log}; + Error -> + Error + end. + +%% We need to make sure we do not end up in an infinit loop +%% Take the number of files of the wrap log and add 2 (for +%% the index and size files). +do_log_open(Name, File, {_, N} = Size, snmp_repair = _Repair, Notify) -> + do_snmp_log_open(Name, File, Size, N+2, Notify); + +do_log_open(Name, File, Size, snmp_repair = _Repair, Notify) -> + do_snmp_log_open(Name, File, Size, 1, Notify); + +do_log_open(Name, File, Size, Repair, Notify) -> + do_std_log_open(Name, File, Size, Repair, Notify). + + +do_snmp_log_open(Name, File, Size, N, Notify) when N =< 0 -> + do_std_log_open(Name, File, Size, true, Notify); +do_snmp_log_open(Name, File, Size, N, Notify) -> + case do_std_log_open(Name, File, Size, true, Notify) of + {error, {not_a_log_file, XFile}} -> + case file:rename(XFile, lists:append([XFile, ".MOVED"])) of + ok -> + ?vinfo("Failed open log file (even with repair) - " + "not a logfile:" + "~n Attempting to move file aside (.MOVED)" + "~n ~s", [XFile]), + do_snmp_log_open(Name, File, Size, N-1, Notify); + Error -> + {error, {rename_failed, Error}} + end; + {error, Reason} -> + ?vinfo("Failed open log file (even with repair) - " + "~n Attempting to move old log file aside (.MOVED)" + "~n~p", [Reason]), + move_log(File), + do_std_log_open(Name, File, Size, true, Notify); + Else -> + Else + end. + + +%% First try to open the log without the size-spec. This will +%% succeed if the log has already been created. In that case, +%% we'll use whatever size the log had at the time it was closed. +do_std_log_open(Name, File, Size, Repair, Notify) -> + Opts = [{name, Name}, + {file, File}, + {type, ?LOG_TYPE}, + {format, ?LOG_FORMAT}, + {mode, read_write}, + {notify, Notify}, + {repair, Repair}], + case disk_log:open(Opts) of + {error, {badarg, size}} -> + %% The log didn't exist, try with the size-spec + disk_log:open([{size, Size} | Opts]); + Else -> + Else + end. + + +log_open(Name, File) -> + Opts = [{name, Name}, + {file, File}, + {type, ?LOG_TYPE}, + {format, ?LOG_FORMAT}, + {mode, read_only}], + case disk_log:open(Opts) of + {error, {badarg, size}} -> + {error, no_such_log}; + Else -> + Else + end. + + +move_log(File) -> + Dir = filename:dirname(File), + FileName = filename:basename(File), + case file:list_dir(Dir) of + {ok, Files0} -> + Files = [F || F <- Files0, lists:prefix(FileName, F)], + F = fun(XFile) -> + file:rename(XFile, lists:append([XFile, ".MOVED"])) + end, + lists:foreach(F, Files); + _ -> + ok + end. + + +is_owner(Log) -> + lists:member(self(), log_owners(Log)). + +log_owners(Log) -> + Info = log_info(Log), + case lists:keysearch(owners, 1, Info) of + {value, {_, Pids}} -> + [P || {P, _} <- Pids]; + _ -> + [] + end. + +log_info(Log) -> + case disk_log:info(Log) of + Info when is_list(Info) -> + Info; + _ -> + [] + end. + + +timestamp() -> + {calendar:local_time(), calendar:universal_time()}. + |