aboutsummaryrefslogtreecommitdiffstats
path: root/lib/snmp/src/misc/snmp_log.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/snmp/src/misc/snmp_log.erl')
-rw-r--r--lib/snmp/src/misc/snmp_log.erl584
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()}.
+