.title arpwatch .ident "X1-003" ;+ ; Version: X1-003 ; ; Facility: Diagnostic Utilities. ; ; Abstract: This is similar to LATWATCH but will only process ARP ; protocol messages. ; ; Environment: PHY_IO privilege is needed. ; ; The following is the CLD used in ARPWATCH: ; ; module arpwatch_cld ; ; define verb arpwatch ; noparameters ; qualifier debug ; qualifier device, value (required) ; qualifier display, default, value (type = display_options) ; qualifier both ; qualifier from, default, value (default = "*") ; qualifier to, default, value (default = "*") ; qualifier nonames ; qualifier begin, value (required, type = $datetime) ; qualifier end, value (required, type = $datetime) ; qualifier count, value (required, type = $number) ; qualifier output, ; value (type = $outfile, default = "ARPWATCH.LOG") ; qualifier record, ; value (type = $outfile, default = "ARPWATCH.RECORD") ; qualifier playback, ; value (type = $infile, default = "ARPWATCH.RECORD") ; qualifier generate, ; value (type = $outfile, default = "ARPWATCH.INDEX") ; qualifier header ; ; disallow (end and count) ; disallow (record and playback) ; disallow (playback and (begin or end)) ; ; define type display_options ; keyword all ; keyword both ; keyword none ; keyword ascii, default ; keyword text ; keyword hexadecimal ; keyword fast ; ; History: ; ; 31-Aug-1994, DBS; Version X1-001 ; 001 - Original version. (Taken from LATWATCH.) ; 01-Sep-1994, DBS; Version X1-002 ; 002 - Added /GENERATE to generate an indexed file containing internet to ; ethernet address resolutions. ; 02-Sep-1994, DBS; Version X1-003 ; 003 - Added /HEADER to be used if you want to see the ethernet headers. ; I decided to trun these OFF by default in this program. ;- .subtitle Psect definitions, external definitions .library "SYS$LIBRARY:LIB.MLB" .library "SYS$LIBRARY:STARLET.MLB" .library "DBSLIBRARY:SYS_MACROS.MLB" .link "SYS$SYSTEM:SYS.STB" /selective_search .disable global .external arpwatch_cld .external cli$dcl_parse .external cli$get_value .external cli$present .external lan_format_header .external lan_startup_prm .external lib$free_vm .external lib$get_foreign .external lib$get_vm .external lib$init_timer .external lib$lookup_key .external lib$show_timer .external lib$show_vm .external lib$signal .external lib$stop .external lib_cvt_t_l .external lib_output_seg_t .external lib_output_seg_tzb .external lib_output_seg_zb .external str$match_wild .external str_collapse .external str_len .external str_uppercase .external sys_find_ether_device .external sys_trap_controlc enb_long ; for structured macro stuff _landef _lanhdrdef $clidef $climsgdef $fabdef $iodef $lnmdef $psldef $rabdef $rmsdef $ssdef $stsdef def_psect _util_data, type=DATA, alignment=LONG def_psect _util_code, type=CODE, alignment=LONG .subtitle Local macros ; .macro IF_PRESENT ; All we do here is set bits in cli_defaulted and cli_present to indicate ; which qualifiers were used and whether or not they used the default. ; This information is used later for matching packet information against ; what the user wants to see. .macro if_present item call cli$present cli_'item if then bisl #m_'item, cli_defaulted else if then bisl #m_'item, cli_supplied endif endif .endm if_present .macro vector location, value .address location .long value .endm vector .macro check_protocol type, ?next cmpb #apn_'type, r9 bnequ next call display_'type'_packet brw exit1 next: .endm check_protocol .macro swapbl source, destination .if blank source .error 0 ; Input argument missing from SWAPBL .mexit .endc .if blank destination .error 0 ; Output argument missing from SWAPBL .mexit .endc .if identical source,R0 .error 0 ; Input argument for SWAPBL cannot be R0 .mexit .endc movl source, r0 rotl #8, r0, -(sp) movb r0, 3(sp) extzv #16, #8, r0, r0 movb r0, 1(sp) movl (sp)+, destination .endm swapbl .macro swapbw source, destination .if blank source .error 0 ; Input argument missing from SWAPBW .mexit .endc .if blank destination .error 0 ; Output argument missing from SWAPBW .mexit .endc .if identical source,R0 .error 0 ; Input argument for SWAPBW cannot be R0 .mexit .endc extzv #8, #8, source, r0 extzv #0, #8, source, r1 ashl #8, r1, r1 bisl2 r1, r0 movl r0, destination .endm swapbw .subtitle Impure data areas set_psect _util_data program_id: .ascid "ARPWATCH X1-003" loaded_fao: .ascid "!UL names and addresses were loaded" intro_line1: .ascid "Starting a watch on device !AS" intro_line2: .ascid "ARP packets from !AS [!AS] to !AS [!AS]!/"- "Display option is !AS" playing_back: .ascid "Reading recorded data from !AS" display_header: .ascid "!78*-!/From !AS [!AC] to !AS [!AC]"- "!/ !4UW byte buffer at !%D" wildcard: .ascid "*" null_ascic: .ascic "" empty_string: .ascid " -----" vm_allocated: .ascid "LIB$GET_VM allocated !UL bytes at !XL" tally: .ascid "Of the !UL ARP packets read "- "(!UL total), !UL packets were !AS" displayed: .ascid "displayed" recorded: .ascid "recorded" faol_prmlst: .blkl 40 .long ^XFFBADBAD arp_header: .ascid "Network type !UW, Prot type !XW, Address Len "- "Inet !UB, Subnet !UB, OpType !UW" arp_request: .ascid " Request from !UB.!UB.!UB.!UB (!XB-!XB-!XB-!XB-!XB-!XB)!/"- " for the hardware address of !UB.!UB.!UB.!UB" arp_response: .ascid " Reply to !UB.!UB.!UB.!UB (!XB-!XB-!XB-!XB-!XB-!XB)!/"- " ... the hardware address of !UB.!UB.!UB.!UB is "- "!XB-!XB-!XB-!XB-!XB-!XB" alloc_string _faobuf, 2048 ; used for all output - needs to ; be big enough to hold an ethernet ; packet (in case /disp=fast is used) alloc_string command, 256 ; Here we have to fudge the command line by loading the verb and then ; tacking on whatever the user specifies to the end of it. This we can ; then pass on the the cli routines. command_buffer: .long command_buffer_extra-command_buffer_t .address command_buffer_t command_buffer_t: .ascii "ARPWATCH" command_buffer_extra: .blkb <8+256> ; Some general bits and pieces arp_protocol = ^X0608 iosb: .word 0 iosb_xfr_size: .word 0 iosb_unused1: .byte 0 iosb_status: .byte 0 iosb_error_summary: .byte 0 iosb_unused2: .byte 0 log_channel: .long 0 packets_read: .long 0 ; used for some stats on the way arp_packets_read: .long 0 ; out... packets_displayed: .long 0 ; ... stats_context: .long 0 ; used by lib$init/show_timer ; These next bits are used to convert decnet node numbers in the format ; area.node to aa type addresses area_number: .long 0 node_number: .long 0 area_number_s: .long 0 area_number_addr: .long 0 node_number_s: .long 0 node_number_addr: .long 0 aa_format: .ascid "AA-00-04-00-!XB-!XB" ; The following definitions specifiy the sizes of the text representations ; of the various fields that are used. s_address = 17 ; xx-xx-xx-xx-xx-xx s_name = 32 ; maximum allowed name size s_sap = 23 s_protocol = 5 ; The next bits are used to set up some virtual memory to contain the ; name and address lists (for use by lib$lookup_key) and pointers to the ; lists and data area. vm_bytecount: .long 0 ; used by lib$get_vm/lib$free_vm vm_base_address: .long 0 address_vector_size: .long 0 ; filled in later name_vector_size: .long 0 data_area_size: .long 0 max_nodecount = 30000 ; we won't load any more than this ; many nodes from the nodelist user_nodecount: .long 0 ; this is from reading nodelist node_count: .long 0 ; this is how many real nodes we got address_vector: .long 0 ; these will contain pointers to name_vector: .long 0 ; the memory allocated by lib$get_vm name_address_data: .long 0 ; These are some dummy headers that we need for various routines to point ; to the header and data buffers. packet_header_ds: .long lanhdr_s_lanhdrdef packet_header_addr: .address packet_header packet_data_ds: .long lan_s_ethernet packet_data_addr: .address packet_data ; This is the buffer where the header and data are returned by $qio packet_length: .long 0 ; length of received packet ; The following record buffer structure must remain intact... ; When /RECORD is used, the packet_buffer gets loaded by the $QIO, we then ; load the record type and date/time then write it out to the recording file. record_buffer: rec_type: .byte 0 ; indicates binary/ascii rec_ascii = 1 rec_binary = 2 rec_time: ASSUME REC_TIME EQ .quad 0 ; date/time the record was written rec_hdrsize = .-record_buffer packet_buffer: packet_header: ASSUME PACKET_HEADER EQ .blkb lanhdr_s_lanhdrdef ; contains ethernet packet header packet_data: ASSUME PACKET_DATA EQ .blkb lan_s_ethernet ; contains ethernet packet data packet_buffer_end: ASSUME PACKET_BUFFER_END EQ rec_rsize = .-record_buffer ; Here we define some descriptors that will be used to check the details ; of the incoming packet. We basically have three strings, the source and ; destination address and the sap details (returned by LAN_FORMAT_HEADER). ; We then use "fake" descriptors to address sub-fields within the sap details. ; The strings are those returned by lan_format_header and are therefore ; fixed length and a known format. ; pkt_destination and pkt_source will look like "XX-XX-XX-XX-XX-XX". ; pkt_sap will look like "XX-XX XX XX-XX-XX-XX-XX", where "XX-XX" is the ; protocol (also DSAP plus SSAP), the "XX" is the control byte and the rest ; is the PID consisting of COPID and IPID fields. pkt_destination: .long s_address .address pkt_destination_t pkt_destination_t: .blkb s_address pkt_source: .long s_address .address pkt_source_t pkt_source_t: .blkb s_address pkt_sap: .long s_sap .address pkt_sap_t pkt_sap_t: .blkb s_sap pkt_protocol: .long s_protocol .address pkt_sap_t+0 ; The following areas are used to store the items returned from the command ; line. The sizes here should give enough room for valid addresses etc. ; and also allow for the use of keywords on those items that allow keywords. alloc_string from, 64 alloc_string to, 64 alloc_string device, 64 alloc_string display, 32 alloc_string begin, 64 alloc_string end, 64 alloc_string count, 32 alloc_string output, 255 alloc_string record, 255 alloc_string generate, 255 alloc_string from_name, s_name ; filled in if available from the alloc_string to_name, s_name ; nodelist entries from_ascic: .long 0 to_ascic: .long 0 default_end: .ascid "0 00:30:00.00" begin_time: .quad 0 end_time: .quad 0 count_l: .long 0 ; use time limit lnm_tabnam: .ascid "LNM$PROCESS_TABLE" lnm_lognam: .ascid "SYS$OUTPUT" lnm_acmode: .long psl$c_user lnm_itmlst: .word 0 ; buffer length - filled in later .word lnm$_string .long 0 ; buffer address - filled in later .long 0 ; return address - not used .long 0 ; to end the list destination_ascic: .address null_ascic ; filled in by lookup_name when source_ascic: .address null_ascic ; processing a packet display_option: .long display_c_ascii ; the default display_c_all = 1 display_c_none = 2 display_c_ascii = 3 display_c_hex = 4 display_c_fast = 5 alloc_string display_choice, 16 ; filled in by lib$lookup_key to ; be the full keyword all_segment_size: .long 16 hex_segment_size: .long 20 ascii_segment_size: .long 64 fast_format: .ascid "!AF" ; minimum formatting required ; Here are the valid command line qualifiers. cli_debug: .ascid "DEBUG" cli_device: .ascid "DEVICE" cli_both: .ascid "BOTH" cli_display: .ascid "DISPLAY" cli_from: .ascid "FROM" cli_to: .ascid "TO" cli_nonames: .ascid "NONAMES" cli_count: .ascid "COUNT" cli_end: .ascid "END" cli_begin: .ascid "BEGIN" cli_output: .ascid "OUTPUT" cli_record: .ascid "RECORD" cli_playback: .ascid "PLAYBACK" cli_generate: .ascid "GENERATE" cli_header: .ascid "HEADER" ; The following definitions are used for checking whether a command line ; item was defaulted or not. This is used in an attempt to increase the speed ; with which we determine whether or not we display a particular packet. ; The flags are set by the command parsing routine. flag1: .long 0 ; this is a general flag area but uses ; the stuff defined below... cli_supplied: .long 0 ; to keep track of what was given cli_defaulted: .long 0 ; and what was defaulted m_from = 1 v_from = 0 m_to = 2 v_to = 1 m_both = 512 v_both = 9 m_unknown = 1024 v_unknown = 10 m_from_unknown = 2048 v_from_unknown = 11 m_to_unknown = 4096 v_to_unknown = 12 m_device = 8192 v_device = 13 m_display = 16384 v_display = 14 m_source_unknown = 32768 v_source_unknown = 15 m_dest_unknown = 65536 v_dest_unknown = 16 m_nonames = 131072 v_nonames = 17 m_debug = 262144 v_debug = 18 m_count = 1048576 v_count = 20 m_end = 2097152 v_end = 21 m_begin = 4194304 v_begin = 22 m_output = 8388608 v_output = 23 m_record = 16777216 v_record = 24 m_playback = 33554432 v_playback = 25 m_generate = 67108864 v_generate = 26 m_header = 134217728 v_header = 27 ; The following keyword table (for lib$lookup_key) is used to check the ; single entry UNKNOWN to be used in /from and /to choices. unknown_vector: .long 1*2 ; only one keyword .address unknown_keyword .long m_unknown ; value to return unknown_keyword: .ascic "UNKNOWN" unknown_ascid: .ascid "UNKNOWN" ; not part of the table, but used ; The following keyword table (for lib$lookup_key) contains the valid options ; available on the /display qualifier. display_vector: .long 7*2 ; seven choices available vector ascii_keyword, display_c_ascii vector text_keyword, display_c_ascii vector all_keyword, display_c_all vector both_keyword, display_c_all vector none_keyword, display_c_none vector hex_keyword, display_c_hex vector fast_keyword, display_c_fast all_keyword: .ascic "ALL" both_keyword: .ascic "BOTH" none_keyword: .ascic "NONE" ascii_keyword: .ascic "ASCII" text_keyword: .ascic "TEXT" hex_keyword: .ascic "HEXADECIMAL" fast_keyword: .ascic "FAST" ; Here we define offsets for the ARP packet arp_w_network_type = 0 arp_w_protocol_type = 2 arp_b_subnet_addr_len = 4 arp_b_inet_addr_len = 5 arp_w_operation = 6 arp_c_request = 1 arp_c_response = 2 arp_c_rev_req = 3 arp_c_rev_resp = 4 arp_c_headersize = 8 ; that's fixed arp_r_sender_subnet = 8 ; this is very presumptuous... arp_s_sender_subnet = 6 arp_l_sender_inet = 14 arp_r_target_subnet = 18 arp_s_target_subnet = 6 arp_l_target_inet = 24 .subtitle Work areas for reading of the nodelist file set_psect _util_data _rsize = 256 ; not really but should be plenty _record: .blkb _rsize ; buffer for the record _record_ds: .long _rsize ; descriptor to point to the buffer _record_addr: .address _record alloc_string play_thing, _rsize ; used for juggling the record alloc_string play_thing2, _rsize _mbc = 127 _mbf = 16 _rtv = 255 .align long _fab: $fab fac=, - fnm=, - dnm=, - fop=, - rtv=_rtv, - shr= _rab: $rab fab=_fab, - mbc=_mbc, - mbf=_mbf, - rac=, - rop=, - ubf=_record, - usz=_rsize reset_psect .subtitle Work areas for recording/playback file set_psect _util_data rec_alq = 2400 ; initial allocation rec_deq = 2400 ; default extension quantity rec_mbc = 127 ; multi block count rec_mbf = 16 ; multi buffer count rec_mrs = rec_rsize ; maximum record size rec_rtv = 255 ; retreival pointers .align long rec_fab: $fab alq=rec_alq, - deq=rec_deq, - dnm=, - fop=, - mrs=rec_mrs, - org=, - rat=, - rfm=, - rtv=rec_rtv, - shr= rec_rab: $rab fab=rec_fab, - mbc=rec_mbc, - mbf=rec_mbf, - rac=, - rbf=record_buffer, - rop=, - ubf=record_buffer, - usz=rec_rsize reset_psect .subtitle Work areas for /generated file set_psect _util_data gen_b_flag = 0 gen_c_inet_enet = 1 gen_c_inet_name = 2 gen_c_enet_inet = 3 gen_l_inet_addr = 1 gen_r_enet_addr = 1 gen_s_enet_addr = 6 gen_w_mbz = 5 gen_b_spare = 7 gen_s_data = 32 gen_t_data = 8 gen_record: .blkb 1 ; for flag byte .blkb 6 ; for address key gen_keysize = .-gen_record .blkb 1 ; spare... .blkb gen_s_data gen_rsize = .-gen_record gen_mbf = 16 ; multi buffer count gen_rtv = 255 ; retreival pointers .align long gen_fab: $fab dnm=, - fac=, - fop=, - mrs=gen_rsize, - org=, - rat=, - rfm=, - rtv=gen_rtv, - shr=, - xab=gen_xabkey0 gen_xabkey0: $xabkey dan=0, - dtp=, - ian=1, - flg=, - prolog=3, - ref=0, - pos=<0>, - siz=<7>, - nxt=gen_xaball0 gen_xaball0: $xaball aid=0, - alq=180, - bkz=12, - deq=48, - nxt=gen_xaball1 gen_xaball1: $xaball aid=1, - alq=24, - bkz=12, - deq=12 gen_rab: $rab fab=gen_fab, - kbf=gen_record, - ksz=gen_keysize, - krf=0, - mbf=gen_mbf, - rac=, - rbf=gen_record, - rsz=gen_rsize, - rop= reset_psect .subtitle Mainline set_psect _util_code .entry - arpwatch, ^m<> call lib$init_timer - ; initialize stats in case we stats_context ; want to look at them call sys_trap_controlc - ; so we can exit gracefully process_controlc, - ; ... (hopefully) iosb call read_nodelist ; need some of this info when ; parsing the command... call parse_command ; get all the gory details call startup_device ; fire up the controller $hiber_s ; ...and wait 90$: ret .subtitle Read the nodelist file .entry - read_nodelist, ^m ;++ ; Functional Description: ; The first thing we do is a quick scan of the nodelist file to see ; how much memory we need to allocate for the names and addresses. ; Here we open the nodelist file, read each record in the file and ; pass it on to the process_nodelist_entry routine to validate it ; and store it in the appropriate tables. When complete we just ; close the file. If we don't find the file, we don't generate any ; errors, we just continue but don't have any names and addresses in ; the tables so every address is UNKNOWN when we display the packet ; details. ; ; Calling Sequence: ; call read_nodelist ; ; Formal Argument(s): ; None. ; ; Implicit Inputs: ; None. ; ; Implicit Outputs: ; None. ; ; Completion Codes: ; None ; ; Side Effects: ; None ;-- call scan_nodelist ; to see how many are there clrl node_count ; we use this for counting movl address_vector, r2 ; we will use this later addl #4, r2 ; skip the count movl name_vector, r3 ; we need this also addl #4, r3 ; skip the count movl name_address_data, r4 ; we need this as well $open fab=_fab ; open the nodelist blbc r0, close $connect rab=_rab ; connect to it blbc r0, close 10$: $get rab=_rab ; grab a record blbc r0, close movzwl _rab+rab$w_rsz, r5 ; these get passed on to the movl _rab+rab$l_rbf, r6 ; processing routine call process_nodelist_entry cmpl node_count, - ; how many have we done so far user_nodecount blss 10$ ; still got room, try again close: $close fab=_fab mull3 #2, node_count, - ; setup the vector count to be the @address_vector ; number of longwords (2 per node) movl @address_vector, - ; should be the same number of entries @name_vector ; in each list ret .subtitle Routine to scan the nodelist and allocate virtual memory .entry - scan_nodelist, ^m<> ;+ ; Here we do a quick scan of the nodelist file to see how many entries are ; in it. We assume each line is valid so that if there are comments and ; blank lines we will over allocate the real space that we need. (I can live ; with that). ;- clrl user_nodecount $open fab=_fab ; open the nodelist blbc r0, 90$ $connect rab=_rab ; connect to it blbc r0, 90$ 10$: $get rab=_rab ; grab a record blbc r0, 90$ incl user_nodecount brb 10$ 90$: $close fab=_fab if then movl #max_nodecount, user_nodecount endif if then ; if the file is missing, or empty movl #1, user_nodecount ; allocate space for 1 node to bisl #m_nonames, cli_supplied; prevent accvio's, and set /nonames endif mull3 #8, user_nodecount, - ; that's how many bytes in the vector address_vector_size addl2 #4, address_vector_size ; allow for the vector count movl address_vector_size, - ; they will be the same size name_vector_size addl3 #s_address, #s_name, - ; that's the size of each entry data_area_size addl2 #2, data_area_size ; plus byte count overhead mull2 user_nodecount, - ; and this is how much we really need data_area_size ; for the data portion mull3 #2, address_vector_size, - vm_bytecount addl2 data_area_size, - ; this is the total allocation we vm_bytecount ; need call lib$get_vm - ; now try to allocate it vm_bytecount, - vm_base_address if then ; and signal any problems signal code=r0 endif movl vm_base_address, - ; that's the pointer to the address address_vector ; vector addl3 address_vector_size, - ; this is the pointer to the name address_vector, - ; vector name_vector addl3 name_vector_size, - ; and the pointer to the data area name_vector, - name_address_data clrl @address_vector ; in case we have no entries clrl @name_vector ret .subtitle Process a nodelist entry .entry - process_nodelist_entry, ^m<> ;++ ; Functional Description: ; This routine will verify (in a limited way) the record from the ; nodelist file. Blank lines are allowed, comments can begin with ; either "!" or ";" and can really be any line without an equals sign ; in it. ; The expected format of the record is ; xx-xx-xx-xx-xx-xx = nodename ; Where the xx-xx... stuff is the address of the node with name ; nodename. All characters are converted to uppercase. ; Once we get a "valid" record, we load it into the address and name ; tables, sort out the cross referencing between the two and that's ; that. ; ; Calling Sequence: ; call process_nodelist_entry ; ; Formal Argument(s): ; None. ; ; Implicit Inputs: ; R2 pointer to next longword entry in address vector ; R3 pointer to next longword entry in name vector ; R4 pointer to next byte in name/address data ; R5 size of record ; R6 address of record buffer ; ; Implicit Outputs: ; R5 and R6 are trashed to reflect the size of the record after we ; have done an str_collapse. ; ; Completion Codes: ; None ; ; Side Effects: ; None ;-- movl r5, _record_ds ; load up a descriptor for us to movl r6, _record_addr ; use call str_uppercase - ; convert the record to uppercase play_thing_ds, - _record_ds call str_collapse - ; get rid of spaces etc... play_thing2_ds, - play_thing_ds call str_len - ; and get the real length play_thing2_ds, - play_thing2 movq play_thing2, r5 ; now point to the real data tstl r5 ; anything there? beql 90$ ; nope, so go away cmpb #^A/!/, (r6) ; is this a comment beql 90$ ; yep, so ignore it cmpb #^A/;/, (r6) ; allow ";" as a comment as well beql 90$ ; yep, so ignore it cmpb #^A/=/, (r6) ; see if address is null beql 90$ ; yep, so ignore it locc #^A/=/, r5, (r6) ; look for an "=" cmpl r0, #1 ; how many characters left? bleq 90$ ; not enough, so skip it subl3 r6, r1, r7 ; R7 = address length movl r4, (r2)+ ; pointer to address in address vector movb r7, (r4)+ ; move the address byte count to the ; data area 10$: movb (r6)+, (r4)+ ; now move the address sobgtr r7, 10$ ; until we have it all movl r4, (r2)+ ; pointer to name in address table decl r0 ; this skips the "=" by reducing the ; length by one incl r6 ; and upping the address by one cmpl r0, #s_name ; check that the name is not too long bleq 15$ ; it's ok, so keep going movl #s_name, r0 ; force it to our maximum length 15$: movb r0, (r4)+ ; move the name byte count to the ; data area 20$: movb (r6)+, (r4)+ ; now move the name sobgtr r0, 20$ ; until it's all done movl -4(r2), (r3)+ ; pointer to name in name vector movl -8(r2), (r3)+ ; pointer to address in name vector incl node_count ; keep track of how many we have 90$: ret .subtitle Parse the command .entry - parse_command, ^m<> ;++ ; Functional Description: ; Here we validate the command and extract everything we can and ; validate any qualifier values that were suppplied. ; ; Calling Sequence: ; call parse_command ; ; Formal Argument(s): ; None ; ; Implicit Inputs: ; None. ; ; Implicit Outputs: ; None. ; ; Completion Codes: ; None ; ; Side Effects: ; None ;-- pushaw command ; here we get the command they pushl #0 ; used to invoke us - we don't pushaq command_ds ; prompt even if they didn't give calls #3, g^lib$get_foreign ; any options pushr #^m movc3 command, - ; tack the foreign command command_t, - ; onto the primed buffer command_buffer_extra popr #^m addl2 command, command_buffer ; and fixup the length call cli$dcl_parse - ; see if the cli routines think command_buffer, - ; the command is ok arpwatch_cld if then ; if it didn't work bisl #sts$m_inhib_msg, r0 ; then don't signal when we exit pushl r0 ; cli$... has already done that calls #1, g^lib$stop ; bail out endif ; then if_present from ; here we check all the possible if_present to ; qualifiers and set bits in if_present device ; the cli_supplied and _defaulted if_present display ; flags to say what was what if_present both if_present nonames if_present debug if_present count if_present end if_present output if_present record if_present playback if_present generate if_present header ; get the output details if any if then call cli$get_value cli_output, output_ds, output movw output, lnm_itmlst movab output_t, lnm_itmlst+4 $crelnm_s - tabnam=lnm_tabnam, - lognam=lnm_lognam, - acmode=lnm_acmode, - itmlst=lnm_itmlst if then signal code=r0 endif endif ; now see if we are recording or playing back if then call cli$get_value cli_record, record_ds, record call create_record_file bisl #m_nonames, cli_supplied ; prevent name processing endif if then call cli$get_value cli_playback, record_ds, record endif ; see if we are creating an index file if then call cli$get_value cli_generate, generate_ds, generate call open_index_file endif display program_id ; say who we are $fao_s ctrstr=loaded_fao, - ; say how many names/addresses we outbuf=_faobuf_ds, - ; loaded outlen=_faobuf, - p1=node_count display _faobuf ; Now we get the values they supplied, if any if then call cli$get_value - ; they supplied a device, so use it cli_device, device_ds, device else call sys_find_ether_device - ; else see what's on the system device_ds ; and use that one if then ; if any problems at this point pushl r0 calls #1, g^lib$stop ; bail out endif ; then call str_len - ; we got one, now fix up the device_ds, - ; string device endif ; then ; ...now get all the other values, there should be no errors since all these ; have defaults i.e. there should not be any cli$_absent returned call cli$get_value cli_from, from_ds, from call cli$get_value cli_to, to_ds, to call cli$get_value cli_display, display_ds, display call check_display_option if then ; if /from not defaulted call check_from_option ; check what they gave us else clrl from_name endif if then ; if /to not defaulted call check_to_option ; check what they gave us else clrl to_name endif if then $fao_s ctrstr=vm_allocated, - outbuf=_faobuf_ds, - outlen=_faobuf, - p1=vm_bytecount, - p2=vm_base_address display _faobuf call lib$show_vm endif if then call cli$get_value cli_begin, begin_ds, begin $bintim_s - timbuf=begin, - timadr=begin_time $setimr_s - efn=#0, - daytim=begin_time $wfland_s - efn=#0, - mask=#1 endif if then call cli$get_value cli_count, count_ds, count call lib_cvt_t_l count, count_l endif if then ; if it's zero decl count_l ; make it -l i.e. use time to end endif bbs #v_count, cli_supplied, - ; if /count specified, then exit_parse ; don't use the timer to exit if then call cli$get_value cli_end, end_ds, end $bintim_s - timbuf=end, - timadr=end_time else $bintim_s - timbuf=default_end, - timadr=end_time endif $setimr_s - daytim=end_time, - astadr=process_controlc exit_parse: ret .subtitle Routines to check the command line qualifier values .entry - check_display_option, ^m<> ;+ ; Here we take what was given and return the full keyword for display ; purposes. There should be no errors since the cli parsing would have ; picked them up and we wouldn't have got this far. ;- call lib$lookup_key - display, - ; this is what they said display_vector, - ; ...the table of valid keywords display_option, - ; ...the coded value display_choice_ds, - ; ...the full keyword display_choice ; ...and the keyword length ret ;+ ; The check_from_option and check_to_option routines do the same work but ; just use different strings as the source. ; The strategy is as follows: ; 1. See if the keyword UNKNOWN was used. If so then set the appropriate ; flag for later... that's all we need to do. ; 2. Now check to see if the string is in the form area.node i.e. a DECnet ; address. If it is, then we convert it into a string of the form ; AA-00-04-00-XX-XX and make it look like the user typed it. ; 3. Assume the string is a name that will be found in the nodename list. ; 4. If we find the name in the nodename list then use the associated address ; as our address used for matching against packets and use the given ; name to display in the header. At this point we have finished. ; 5. If the name is not in the nodename list, assume it is an address in the ; address list. ; 6. If we find it in the address list then use the address for matching ; purposes and use the associated name to display in the header. ; At this point we have finished. ; 7. At this point the given value is neither a known name nor a known ; address therefore just use what we were given as it is probably an ; address that is not currently in the list or contains wildcards. ;- .entry - check_from_option, ^m<> call lib$lookup_key from, unknown_vector if then ; if they said /from=unknown bisl #m_from_unknown, flag1 ; set the flag movq wildcard, from movq unknown_ascid, from_name else pushr #^m movaq from_ds, r10 ; check for a DECnet address movaq from, r11 call convert_decnet_to_aa popr #^m call lib$lookup_key from, @name_vector, from_ascic, from_ds, from if then ; if it is a name pushr #^m movc3 from, @from+4, - ; copy "from" into "from_name" @from_name+4 movl from, from_name ; and fix the length movl from_ascic, r0 ; just to play with movzbl (r0)+, from ; that's the address length movc3 from, (r0), @from+4 ; copy the address to the "from" field popr #^m else ; wasn't a name... call lib$lookup_key from, @address_vector, from_ascic if then ; if it is an address pushr #^m movl from_ascic, r0 movzbl (r0)+, from_name movc3 from_name, (r0), - ; copy the ascic stuff to the ascid @from_name+4 ; area popr #^m else ; wasn't name or address - use as is clrl from_name ; nothing to display endif ; then endif ; then endif ; then ret .entry - check_to_option, ^m<> call lib$lookup_key to, unknown_vector if then ; if they said /to=unknown bisl #m_to_unknown, flag1 ; set the flag movq wildcard, to movq unknown_ascid, to_name else pushr #^m movaq to_ds, r10 ; see if it is a DECnet address movaq to, r11 call convert_decnet_to_aa popr #^m call lib$lookup_key to, @name_vector, to_ascic, to_ds, to if then ; if it is a name pushr #^m movc3 to, @to+4, @to_name+4 ; copy "to" into "to_name" movl to, to_name ; and fix the length movl to_ascic, r0 ; just to play with movzbl (r0)+, to ; that's the address length movc3 to, (r0), @to+4 ; copy the address to the "to" field popr #^m else ; wasn't a name... call lib$lookup_key to, @address_vector, to_ascic if then ; if it is an address pushr #^m movl to_ascic, r0 movzbl (r0)+, to_name movc3 to_name, (r0), - ; copy the ascic stuff to the ascid @to_name+4 ; area popr #^m else ; wasn't name or address - use as is clrl to_name ; nothing to display endif ; then endif ; then endif ; then ret .entry - convert_decnet_to_aa, ^m<> ;+ ; Here we check to see if the specified address is a possible decnet type ; address in the form area.node. If it is then we convert it to the format ; AA-00-04-00-XX-XX and set this up to look like the user supplied this value. ; ; Inputs: ; R10 Address of the output buffer descriptor ; R11 Address of the input buffer descriptor ;- pushr #^m movq (r11), r8 ; grab the input buffer descriptor locc #^A/./, r8, (r9) ; try to find a dot tstl r0 ; is there one? bneq 10$ ; yes, so play with it brw 90$ ; no, bail out 10$: movq (r11), area_number_s ; load the area number descriptor subl2 r0, area_number_s ; and fix up the length decl r0 ; shorten length by one incl r1 ; and increase address by one ; to skip the dot movq r0, node_number_s ; load the node number descriptor call lib_cvt_t_l area_number_s, area_number blbc r0, 90$ ; bail out if no good call lib_cvt_t_l node_number_s, node_number blbc r0, 90$ ; bail out if no good mull3 #1024, area_number, r0 ; now convert the two numbers into addl2 node_number, r0 ; a single word value movl r0, r1 ; we need a copy to split it bicl #^XFFFFFF00, r0 ; now do the jiggery pokery to bicl #^XFFFF00FF, r1 ; get the bytes swapped and divl2 #^X100, r1 ; cleaned up for the aa format $fao_s ctrstr=aa_format, - outbuf=(r10), - outlen=(r11), - p1=r0, - p2=r1 90$: popr #^m ret .subtitle Startup the ethernet device in promiscuous mode .entry - startup_device, ^m<> ;++ ; Functional Description: ; This routine attempts to startup the ethernet device, set it up so ; we can see everything then queue the first read request to it. ; It also displays some informational messages along the way. ; ; Calling Sequence: ; call startup_device ; ; Formal Argument(s): ; None ; ; Implicit Inputs: ; None. ; ; Implicit Outputs: ; None. ; ; Completion Codes: ; None ; ; Side Effects: ; None ;-- if then $fao_s ctrstr=playing_back, - outbuf=_faobuf_ds, - outlen=_faobuf, - p1=#record display _faobuf call open_record_file 10$: $get rab=rec_rab blbc r0, 20$ call process_packet brb 10$ 20$: call process_controlc else $fao_s ctrstr=intro_line1, - outbuf=_faobuf_ds, - outlen=_faobuf, - p1=#device display _faobuf call lan_startup_prm - device, - log_channel, - iosb if then ; if anything goes wrong with that pushl iosb ; then we can't go any further calls #1, g^lib$stop endif ; then $fao_s ctrstr=intro_line2, - outbuf=_faobuf_ds, - outlen=_faobuf, - p1=#from, - p2=#from_name, - p3=#to, - p4=#to_name, - p5=#display_choice display _faobuf call queue_async_read endif ; then ret .subtitle Queue an asynchronous read I/O to the ethernet device .entry - queue_async_read, ^m<> ;++ ; Functional Description: ; Here we issue a read qio to the ethernet device and setup an ast to ; invoke the process_packet routine when we have something to read. ; ; Calling Sequence: ; ; call queue_async_read ; ; Formal Argument(s): ; None ; ; Implicit Inputs: ; ether_watch_data common area. ; ; Implicit Outputs: ; None ; ; Completion Codes: ; None ; ; Side Effects: ; None ;-- $qio_s chan=log_channel, - func=#io$_readvblk, - iosb=iosb, - astadr=process_packet, - p1=packet_data, - ; data location p2=#lan_s_ethernet, - ; size of data area p5=#packet_header ; header location if then signal code=r0 endif ; then 90$: ret .subtitle Lookup a nodename given an address .entry - lookup_name, ^m<> ;++ ; Functional Description: ; Check the ethernet address given and return the address of ascic ; string for the associated name. If the address was not found, then ; we return the address of the name UNKNOWN. ; ; Calling Sequence: ; call lookup_name (%descr(address), %ref(name_location)) ; ; Formal Argument(s): ; address.rt.ds Address of the descriptor for the string containing ; the ethernet address to be checked. ; name_location.wl.r Address of a longword that will contain the ; address of the ascic string containing the node name. ; ; Implicit Inputs: ; 4(ap) Address of descriptor of the ethernet address ; 8(ap) Address of a longword to receive the address of the ascic ; string containing the node name ; ; Implicit Outputs: ; None. ; ; Completion Codes: ; The low bit in R0 can be checked on return to see if the address was ; found or not (saves doing string comparisons etc.). ; ; Side Effects: ; None ;-- pushl 8(ap) pushl address_vector pushl 4(ap) calls #3, g^lib$lookup_key if then movab unknown_keyword, @8(ap) endif ; then ret .subtitle Lookup an address given a nodename .entry - lookup_address, ^m<> ;++ ; Functional Description: ; Check the name given and return the address of ascic string for the ; associated ethernet address. If the name was not found, then we ; return the address of the name UNKNOWN. ; ; Calling Sequence: ; call lookup_name (%descr(nodename), %ref(address_location)) ; ; Formal Argument(s): ; nodename.rt.ds Address of the descriptor for the string containing ; the node name to be checked. ; address_location.wl.r Address of a longword that will contain the ; address of the ascic string containing the ethernet ; address. ; ; Implicit Inputs: ; 4(ap) Address of descriptor of the node name ; 8(ap) Address of a longword to receive the address of the ascic ; string containing the node address ; ; Implicit Outputs: ; None. ; ; Completion Codes: ; The low bit in R0 can be checked on return to see if the name was ; found or not (saves doing string comparisons etc.). ; ; Side Effects: ; None ;-- pushl 8(ap) pushl name_vector pushl 4(ap) calls #3, g^lib$lookup_key if then movab unknown_keyword, @8(ap) endif ; then ret .subtitle Here's where we get to if they type control/c .entry - process_controlc, ^m<> ;++ ; Functional Description: ; This is where we end up if a control c is used. We just exit. ; ; Calling Sequence: ; via an ast... ; ; Formal Argument(s): ; None. ; ; Implicit Inputs: ; None. ; ; Implicit Outputs: ; None. ; ; Completion Codes: ; None ; ; Side Effects: ; None ;-- if then movaq recorded, r0 else movaq displayed, r0 endif $fao_s ctrstr=tally, - outbuf=_faobuf_ds, - outlen=_faobuf, - p1=arp_packets_read, - p2=packets_read, - p3=packets_displayed, - p4=r0 display _faobuf call lib$free_vm - vm_bytecount, - vm_base_address if then call lib$show_vm call lib$show_timer stats_context endif display command_buffer if then call close_index_file endif if then call close_record_file endif if then call close_record_file endif pushl #^X10000001 calls #1, g^lib$stop ret .subtitle Process a packet .entry - process_packet, ^m<> ;++ ; Functional Description: ; This routine gets the packet, formats the header and does the ; necessary checking to see if we want to look at it based on the ; selection criteria supplied by the user on the command line. ; We also queue another i/o request for the next packet. ; ; Calling Sequence: ; call process_packet ; ; Formal Argument(s): ; None. ; ; Implicit Inputs: ; None. ; ; Implicit Outputs: ; None. ; ; Completion Codes: ; None ; ; Side Effects: ; None ;-- incl packets_read if then movzwl rec_rab+rab$w_rsz, packet_length subl #rec_hdrsize, packet_length else movzwl iosb_xfr_size, - ; grab the length of the packet we packet_length ; are going to look at endif cmpw #arp_protocol, - ; see if this is a ARP packet packet_header+lanhdr_w_protocol beql check_header brw exit ; no good, so do no more check_header: incl arp_packets_read ; 'tis ARP so count it call lan_format_header - ; format the header so we can start packet_header_ds, - ; doing our matching pkt_destination, - pkt_source, - pkt_sap bicl #, - flag1 if then call lookup_name - ; get the friendly names if they pkt_destination, - ; exist in our list destination_ascic if then ; couldn't find them in the list bisl #m_dest_unknown, flag1 ; flag an unknown destination endif endif if then call lookup_name - ; see if the source is in our list pkt_source, - ; of known addresses source_ascic if then ; couldn't find this one either bisl #m_source_unknown, flag1 ; flag an unknown source endif endif check_source: bbs #v_from, cli_defaulted, - ; if /from was defaulted check_destination ; it always matches, check destination if then ; if /from=unknown if then ; if source is unknown brb check_destination ; then it's ours, check destination else if - and then brw do_display else brw exit ; else we don't want it endif endif endif call str$match_wild - ; check source of the packet pkt_source, from ; against the /from value blbs r0, check_destination ; it's a winner bbs #v_both, cli_supplied, - ; no match, so bail out unless they 10$ ; specified /both brw exit 10$: call str$match_wild - ; want /both so try to match dest pkt_destination, from ; address blbs r0, check_destination brw exit ; if that failed, just get out check_destination: bbs #v_to, cli_defaulted, - ; if /to was defaulted do_display ; it always matches, so display it if then ; if /to=unknown if then ; if destination is unknown brb do_display ; then we want to look at it else if - and then brw do_display else brb exit ; else we don't want it endif endif endif call str$match_wild - ; check destination of the packet pkt_destination, to ; against the /to value blbs r0, do_display ; it matches, so look at it bbc #v_both, cli_supplied, - ; no match, so bail out unless they exit ; said /both in the command call str$match_wild - ; want /both so try to match source pkt_source, to ; address blbc r0, exit ; if that failed, try another packet do_display: incl packets_displayed if then call record_a_packet else call display_a_packet endif if then call create_index_entry endif exit: cmpl arp_packets_read, count_l ; see if we should finish yet blssu 10$ ; not yet... call process_controlc ; finish, don't want to look anymore 10$: bbs #v_playback, cli_supplied, 90$ call queue_async_read 90$: ret .subtitle Display a packet .entry - display_a_packet, ^m ;++ ; Functional Description: ; Here we display the packet header in a nice formatted way. Then, ; based on the display option chosen, display (or not) the contents ; of the packet in the chosen way. ; All registers are saved on the way in so that the display routines ; can have their way with them. ; ; Calling Sequence: ; call display_a_packet ; ; Formal Argument(s): ; None. ; ; Implicit Inputs: ; None. ; ; Implicit Outputs: ; None. ; ; Completion Codes: ; None ; ; Side Effects: ; None ;-- if then movaq rec_time, r0 else clrl r0 endif if then $fao_s ctrstr=display_header, - outbuf=_faobuf_ds, - outlen=_faobuf, - p1=#pkt_source, - p2=source_ascic, - p3=#pkt_destination, - p4=destination_ascic, - p5=packet_length, - p6=r0 display _faobuf else display empty_string endif movab packet_data, r11 ; address of packet data buffer ; used by all display routines swapbw arp_w_operation(r11), r9 cmpb arp_b_inet_addr_len(r11), #4 bneq 20$ cmpb arp_b_subnet_addr_len(r11), #6 bneq 20$ brw req 20$: moval faol_prmlst, r10 ; address of the parameter list swapbw arp_w_network_type(r11), (r10)+ swapbw arp_w_protocol_type(r11), (r10)+ movzbl arp_b_inet_addr_len(r11), (r10)+ movzbl arp_b_subnet_addr_len(r11), (r10)+ swapbw arp_w_operation(r11), (r10)+ $faol_s ctrstr=arp_header, - outbuf=_faobuf_ds, - outlen=_faobuf, - prmlst=faol_prmlst display _faobuf req: moval faol_prmlst, r10 ; address of the parameter list cmpw #arp_c_response, r9 bnequ do_req brw do_resp do_req: movzbl arp_l_sender_inet(r11), (r10)+ movzbl arp_l_sender_inet+1(r11), (r10)+ movzbl arp_l_sender_inet+2(r11), (r10)+ movzbl arp_l_sender_inet+3(r11), (r10)+ movzbl arp_r_sender_subnet(r11), (r10)+ movzbl arp_r_sender_subnet+1(r11), (r10)+ movzbl arp_r_sender_subnet+2(r11), (r10)+ movzbl arp_r_sender_subnet+3(r11), (r10)+ movzbl arp_r_sender_subnet+4(r11), (r10)+ movzbl arp_r_sender_subnet+5(r11), (r10)+ movzbl arp_l_target_inet(r11), (r10)+ movzbl arp_l_target_inet+1(r11), (r10)+ movzbl arp_l_target_inet+2(r11), (r10)+ movzbl arp_l_target_inet+3(r11), (r10)+ $faol_s ctrstr=arp_request, - outbuf=_faobuf_ds, - outlen=_faobuf, - prmlst=faol_prmlst display _faobuf brw exit1 do_resp: movzbl arp_l_target_inet(r11), (r10)+ movzbl arp_l_target_inet+1(r11), (r10)+ movzbl arp_l_target_inet+2(r11), (r10)+ movzbl arp_l_target_inet+3(r11), (r10)+ movzbl arp_r_target_subnet(r11), (r10)+ movzbl arp_r_target_subnet+1(r11), (r10)+ movzbl arp_r_target_subnet+2(r11), (r10)+ movzbl arp_r_target_subnet+3(r11), (r10)+ movzbl arp_r_target_subnet+4(r11), (r10)+ movzbl arp_r_target_subnet+5(r11), (r10)+ movzbl arp_l_sender_inet(r11), (r10)+ movzbl arp_l_sender_inet+1(r11), (r10)+ movzbl arp_l_sender_inet+2(r11), (r10)+ movzbl arp_l_sender_inet+3(r11), (r10)+ movzbl arp_r_sender_subnet(r11), (r10)+ movzbl arp_r_sender_subnet+1(r11), (r10)+ movzbl arp_r_sender_subnet+2(r11), (r10)+ movzbl arp_r_sender_subnet+3(r11), (r10)+ movzbl arp_r_sender_subnet+4(r11), (r10)+ movzbl arp_r_sender_subnet+5(r11), (r10)+ $faol_s ctrstr=arp_response, - outbuf=_faobuf_ds, - outlen=_faobuf, - prmlst=faol_prmlst display _faobuf exit1: ret .subtitle Display the packet contents according to their wishes .entry - display_packet_contents, ^m<> ; Display the packet based on what /display option was chosen. if then if then pushal ascii_segment_size pushal packet_length pushaq packet_data_ds calls #3, g^lib_output_seg_t else if then pushal all_segment_size pushal packet_length pushaq packet_data_ds calls #3, g^lib_output_seg_tzb else if then pushal hex_segment_size pushal packet_length pushaq packet_data_ds calls #3, g^lib_output_seg_zb else $fao_s ctrstr=fast_format, - outbuf=_faobuf_ds, - outlen=_faobuf, - p1=packet_length , - p2=#packet_data display _faobuf endif ; then endif ; then endif ; then endif ; then ret .subtitle Routines to handle the recording/playback file .entry - create_record_file, ^m<> movab record_t, rec_fab+fab$l_fna movb record, rec_fab+fab$b_fns movb #fab$m_put, rec_fab+fab$b_fac $create fab=rec_fab if then signal code=r0 endif $connect rab=rec_rab if then signal code=r0 endif ret .entry - open_record_file, ^m<> movab record_t, rec_fab+fab$l_fna movb record, rec_fab+fab$b_fns movb #fab$m_get, rec_fab+fab$b_fac $open fab=rec_fab if then signal code=r0 endif $connect rab=rec_rab if then signal code=r0 endif ret .entry - close_record_file, ^m<> $close fab=rec_fab ret .entry - record_a_packet, ^m<> movb #rec_binary, rec_type $gettim_s - timadr=rec_time addl3 packet_length, #rec_hdrsize, r0 addl2 #lanhdr_s_lanhdrdef, r0 movw r0, rec_rab+rab$w_rsz $put rab=rec_rab if then signal code=r0 endif ret .subtitle Routines to handle the /generated file .entry - open_index_file, ^m<> $create fab=gen_fab if then signal code=r0 endif $connect rab=gen_rab if then signal code=r0 endif ret .entry - close_index_file, ^m<> $close fab=gen_fab ret .entry - create_index_entry, ^m movab packet_data, r11 movab gen_record, r10 clrb gen_b_spare(r10) movb #gen_c_inet_enet, - gen_b_flag(r10) movl arp_l_sender_inet(r11), - gen_l_inet_addr(r10) clrw gen_w_mbz(r10) movc3 #gen_s_enet_addr, - arp_r_sender_subnet(r11), - gen_t_data(r10) $put rab=gen_rab movb #gen_c_enet_inet, - gen_b_flag(r10) movc3 #gen_s_enet_addr, - arp_r_sender_subnet(r11), - gen_r_enet_addr(r10) movl arp_l_sender_inet(r11), - gen_t_data(r10) $put rab=gen_rab ret .end arpwatch