home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The World of Computer Software
/
World_Of_Computer_Software-02-385-Vol-1of3.iso
/
p
/
pcrte224.zip
/
SOURCE.ZIP
/
RIP.INC
< prev
next >
Wrap
Text File
|
1992-06-09
|
25KB
|
728 lines
;;*************************************************************************
;; rip.inc rip.inc
;;*************************************************************************
;;
;; Copyright (C) 1989 Northwestern University, Vance Morrison
;;
;;
;; Permission to view, compile, and modify for LOCAL (intra-organization)
;; USE ONLY is hereby granted, provided that this copyright and permission
;; notice appear on all copies. Any other use by permission only.
;;
;; Northwestern University makes no representations about the suitability
;; of this software for any purpose. It is provided "as is" without expressed
;; or implied warranty. See the copywrite notice file for complete details.
;;
;;*****************************************************************************
;;
;; rip.inc provides a ROUTE interface for th rip protocol. In particular
;; it provides the following functions
;;
;; Routines provided by this module
;;
;; RIP_DECLARE name, ip_net, timer, udp, udp_sock
;; RIP_DEFINE name
;; ROUTE_ADD_in_CX_SI_DI_ES_const_DI_ES name
;; ROUTE_FIND_in_AX_BX_CX_out_AX_DL_const_BP_ES name, forme, fail
;;
;;*****************************************************************************
;;*****************************************************************************
;; definition of RIP packet structure
RIP_PORT = 208h
SWAPPED_RIP_PORT = 802h
RIP_REPLY = 2 ;; sending out RIP packets
IP_FAMILY = 2
SWAPPED_IP_FAMILY = 200h
rip_head STRUC
rip_command DB RIP_REPLY
rip_version DB 1
rip_zero1 DW 0
rip_head ENDS
rip_route STRUC
rip_family DW SWAPPED_IP_FAMILY
rip_zero2 DW 0
rip_address DD 0
rip_zero3 DD 0
rip_zero4 DD 0
rip_metric DD 8
rip_route ENDS
;;*****************************************************************************
;; internal Routing table definitions
rip_entry STRUC
re_next DW ? ; this MUST be the first field
re_prev DW ?
re_net DD ?
re_gateway DW ?
re_interface DB ? ; interface MUST come directly before metric
re_metric DB ?
re_flags DB ?
re_timer DB ?
re_beautify DW ?
rip_entry ENDS
rip_table_size = 300
rip_data STRUC
rip_hash DW 256 dup (0)
rip_table rip_entry rip_table_size dup (<>)
rip_table_ptr DW ? ;; end of the routeing table
rip_pack_ctr DW ?
rip_pack_ptr DW ?
rip_pack_out DW ?
rip_if_net DD ?
rip_if_last DD ?
rip_data ENDS
rip_table_end = (rip_table+(rip_table_size*(size rip_entry)))
;; Routing Flags
ROUTE_SILENT = (1 shl 0) ; Do not broadcast via RIP
ROUTE_TRANS = (1 shl 1) ; Transiant, can be changed by RIP
ROUTE_VALID = (1 shl 7) ; a valid table entry
;;******************************************************************************
;; RIP_DECLARE name, ip_net, timer, udp
;; RIP_DECLARE delcares a new routing object called 'name'.
;; 'ip_net' is the IP_NET that the router is associated with,
;; 'timer' is a timer object, and 'udp' is the name of
;; a UDP object (to get RIP packets from)
;;
RIP_DECLARE MACRO name, ip_net, timer, udp
.errb <udp>
.DATA
rip_&name&_ip_net = ip_net
rip_&name&_timer = timer
rip_&name&_udp = udp
rip_&name&_udp_sock = (name*100+1)
rip_&name&_dls = ip_&ip_net&_dls
global rip_&name&_data:rip_data
.CODE
global rip_&name&_read_code:near
global rip_&name&_timer_code:near
global rip_&name&_real_define:near
global rip_&name&_send_dls:near
UDP_SOCK_DECLARE %rip_&name&_udp_sock, %rip_&name&_udp, RIP_PORT
ENDM
;;******************************************************************************
;; RIP_DEFINE name
;; RIP_DEFINE sets aside the memory and acutally does the
;; initialization for the routeing objects 'name'
;;
RIP_DEFINE MACRO name
call rip_&name&_real_define
ENDM
RIP_REAL_DEFINE MACRO name
local around, listen_code
.errb <name>
.DATA
rip_&name&_data rip_data <>
.CODE
jmp around ;; declare code objects
rip_&name&_timer_code:
RIP_TASK name
TIMER_RETURN %rip_&name&_timer
;; this does NOT fall through
rip_&name&_send_dls:
RIP_SEND_DLS name
ret
rip_&name&_read_code:
mov DI, BX
;; this is a violation of layering, but what can I do
IP_R_HEADER_in_ES_out_SI_const_BX_CX_DX_BP_DI_ES %rip_&name&_ip_net
add SI, ip_src
xchg SI, DI
RIP_PROCESS_PACKET_in_CX_SI_DI_ES name
RET
;; this does NOT fall through
listen_code:
;; because of circular dependance (RIP, a low level
;; protocol depending on UDP a high level protocol)
;; we have to defer the definition of this socket
UDP_SOCK_DEFINE %rip_&name&_udp_sock, rip_&name&_read_code
TIMER_RETURN %rip_&name&_timer
;; this does NOT fall through
around:
rip_&name&_real_define:
mov AX, DS ; NULL all data
mov ES, AX
xor AX, AX
mov CX, size rip_data
mov DI, offset rip_&name&_data
rep
stosb
mov rip_&name&_data.rip_table_ptr, offset rip_&name&_data.rip_table-(size rip_entry)
;; start RIP listening for packets
mov AX, 18*0
TIMER_MARK_in_AX_const_CX_BP_ES %rip_&name&_timer, listen_code
;; set the expire task to go off
mov AX, 18*0
TIMER_MARK_in_AX_const_CX_BP_ES %rip_&name&_timer, rip_&name&_timer_code
RET
ENDM
;;******************************************************************************
;; ROUTE_ADD_in_CX_SI_DI_ES name
;; ROUTE_ADD adds the route to described by network pointed to by SI:ES,
;; the gateway pointed to by DI:ES and the metric held in CL and flags
;; (See flag defs above) held in CH, Note for directly connected networks
;; (CL = 0) the 'gateway' (DI:ES) must point to the IP address of this host
;;
ROUTE_ADD_in_CX_SI_DI_ES_const_DI_ES MACRO name
local done, look_for_empty, found_empty, no_wrap, found, new_route
local add_new_gateway, not_bigger, delete_route, delete_trigger
mov AX, ES:[SI]
mov BX, ES:[SI+2]
mov BP, DI ; save DI
IP_COMPUTE_NET_in_AX_BX_out_AX_BX_const_CX_DX_BP_SI_DI_ES rip_&name&_dls
RIP_GET_ENTRY_in_AX_BX_out_SI_DI_const_AX_BX_CX_BP name, found
cmp CL, 16
jae done
mov SI, word ptr rip_&name&_data.rip_table_ptr
look_for_empty:
add SI, size rip_entry
cmp SI, offset rip_&name&_data.rip_table_end
jb no_wrap
mov SI, offset rip_&name&_data.rip_table
no_wrap:
test byte ptr [SI.re_flags], ROUTE_VALID
jz found_empty
cmp SI, word ptr rip_&name&_data.rip_table_ptr
jnz look_for_empty
LOG_PRINT %mylog, L_ROUTE, L_ERR, <Routing table overflow>
jmp done
found_empty:
cmp SI, word ptr rip_&name&_data.rip_table_ptr
jbe not_bigger
mov word ptr rip_&name&_data.rip_table_ptr, SI
not_bigger:
mov [SI.re_prev], DI ;; link this in last in the list
mov [DI.re_next], SI
mov [SI+re_next], 0 ;; fill in next
mov word ptr [SI+re_net], AX
mov word ptr [SI+re_net+2], BX
or CH, ROUTE_VALID ;; fill in flags
mov byte ptr [SI+re_flags], CH
jmp add_new_gateway
delete_trigger:
delete_route:
LOG_PRINT_INET_in_AX_BX %mylog, L_ROUTE, L_INFO, <Rip from bogus host>
RIP_DELETE_in_SI_const_BX_CX_DX_SI_BP name
jmp done
found:
test [SI+re_flags], ROUTE_TRANS ;; cant change a permanent route
jz done
mov DI, BP ;; restore DI
mov DX, ES:[DI+2]
cmp DX, [SI+re_gateway]
jnz new_route
mov byte ptr [SI+re_metric], CL
cmp CL, 16
jae done
mov byte ptr [SI+re_timer], 3
jmp done
new_route:
cmp CL, [SI+re_metric]
jae done ;; old metric less, we are done
add_new_gateway:
mov DI, BP ;; restore DI
mov byte ptr [SI+re_timer], 3
mov byte ptr [SI+re_metric], CL
mov AX, ES:[DI]
mov BX, ES:[DI+2]
mov word ptr [SI+re_gateway], BX
RIP_FIND_IF_in_AX_BX_out_CL_const_AX_BX_SI_DI %rip_&name&_dls, delete_route
mov byte ptr [SI+re_interface], CL
LOG_PRINT %mylog, L_ROUTE, L_INFO, <Adding the Following Route>
RIP_LOG_ROUTE_in_SI_const_all name, L_INFO
done:
mov DI, BP ;; restore DI
ENDM
;;*****************************************************************************
;; ROUTE_FIND_in_AX_BX_CX_out_AX_DL name, forme, fail
;; ROUTE_FIND takes the network number in the AX:BX pair and the
;; host part in CX of a packet address and does one of three things
;; if it is for this host it jumps to 'forme'. If it could find no
;; route it jumps to 'fail'. otherwise outputs the DL interface
;; in DL and the gatway host part in AX of where to send it to.
;;
ROUTE_FIND_in_AX_BX_CX_out_AX_DL_const_BP_ES MACRO name, forme, fail
local found, done, not_direct
RIP_GET_ENTRY_in_AX_BX_out_SI_DI_const_AX_BX_CX_BP name, found
xor AX, AX ; check the default route
xor BX, BX
RIP_GET_ENTRY_in_AX_BX_out_SI_DI_const_AX_BX_CX_BP name, found
jmp fail
found:
mov DX, word ptr [SI+re_interface] ; load DL=interface DH=metric
mov AX, [SI+re_gateway]
or DH, DH
jnz not_direct
cmp AX, CX
jz forme ;; the re_gateway holds my IP address
mov AX, CX ;; for direct routes
not_direct:
done:
ENDM
;;**************************************************************************
;; RIP_TASK is the code that goes off in 30 seconds. Basicly all it does
;; is call RIP_EXPIRE and then resubmit a timer request so it will go off
;; 30 seconds in the future
;;
RIP_TASK MACRO name
local timer_code, rip_loop, packets_sent
timer_code:
RIP_EXPIRE_const_BX_CX_DX_BP name
call rip_&name&_send_dls
mov AX, 18*30 ;; resubmit the task 30 seconds from now
TIMER_MARK_in_AX_const_CX_BP_ES %rip_&name&_timer, timer_code
ENDM
;;**************************************************************************
;; RIP_SEND_DLS sends out a RIP update.
;;
RIP_SEND_DLS MACRO name
IRP idx, <1,2,3,4,5,6,7,8>
if idx le rip_&name&_dls
RIP_SEND_DL name, idx
endif
endm
ENDM
;;**************************************************************************
;; RIP_SEND_DL generates all the RIP packets for the data link interface 'mydl'
;;
RIP_SEND_DL MACRO name, mydl
local done, pack_loop
.errb <mydl>
mov AX, word ptr dl_ip_&mydl&_flags
test word ptr dl_ip_&mydl&_flags, ROUTE_DL_SILENT ;; skip silent ifs
jnz done
mov AX, word ptr dl_ip_&mydl&_ip
mov BX, word ptr dl_ip_&mydl&_ip+2
IP_COMP_NET_in_AX_BX_out_AX_BX_const_CX_DX_BP_SI_DI_ES rip_&name&_dls
mov word ptr rip_&name&_data.rip_if_net, AX
mov word ptr rip_&name&_data.rip_if_net+2, BX
RIP_INIT_ENTRY_out_SI_const_AX_BX_CX_DX_BP_DI name
mov rip_&name&_data.rip_pack_out, SI
pack_loop:
mov CX, 512 ;; maximum RIP packet size
mov DX, RIP_PORT
mov AX, word ptr dl_ip_&mydl&_broad
mov BX, word ptr dl_ip_&mydl&_broad+2
UDP_SOCK_W_ACCESS_in_AX_BX_CX_DX_out_AX_DI_ES %rip_&name&_udp_sock
cmp AX, 0
jnz done
mov DL, mydl
mov DH, byte ptr dl_ip_&mydl&_metric
mov SI, rip_&name&_data.rip_pack_out
RIP_MAKE_PACKET_in_DX_SI_DI_out_CX_SI_const_BP name, rip_&name&_data.rip_if_net, done
mov rip_&name&_data.rip_pack_out, SI
UDP_SOCK_W_WRITE_in_CX %rip_&name&_udp_sock
jmp pack_loop
done:
ENDM
;;**************************************************************************
;; RIP_DELETE_in_SI_const_BX_CX_DX_SI_BP deletes the entry pointed by by SI
;;
RIP_DELETE_in_SI_const_BX_CX_DX_SI_BP MACRO name
local end_of_list
mov byte ptr [SI+re_flags], 0 ;; make the route invalid
mov DI, [SI+re_prev]
mov AX, [SI+re_next]
mov [DI.re_next], AX
or AX, AX
jz end_of_list
xchg AX, DI
mov [DI+re_prev], AX
end_of_list:
ENDM
;;**************************************************************************
;; RIP_EXPIRE should be called every 30 seconds. This routine decrements
;; the timer entries on the routes and expires the routes after 90 seconds.
;;
RIP_EXPIRE_const_BX_CX_DX_BP MACRO name
local expire_loop, done, delete_route
.errb <name>
RIP_INIT_ENTRY_out_SI_const_AX_BX_CX_DX_BP_DI name
expire_loop:
RIP_NEXT_ENTRY_in_SI_out_SI_const_AX_BX_CX_DX_BP_DI name, done
test [SI+re_flags], ROUTE_TRANS ;; don't expire permenent routes
jz expire_loop
sub [SI+re_timer], 1 ;; decrement the timer
jg expire_loop
jz delete_route
;; check to see if we should purge it from the table
cmp [SI+re_timer], -3
jg expire_loop
RIP_DELETE_in_SI_const_BX_CX_DX_SI_BP name
jmp expire_loop
delete_route:
;; delete the route
mov [SI+re_metric], 16
LOG_PRINT %mylog, L_ROUTE, L_INFO, <Route expired. Deleting route>
RIP_LOG_ROUTE_in_SI_const_all name, L_INFO
jmp expire_loop
done:
ENDM
;;**************************************************************************
;; RIP_PROCESS_PACKET processes the packet in ES:SI. The IP address of
;; the gateway that sent the packet is pointed to by ES:DI. CX holds
;; the length of the RIP part of the packet
RIP_PROCESS_PACKET_in_CX_SI_DI_ES MACRO name
local process_loop, done, next
mov AX, word ptr ES:[DI]
mov BX, word ptr ES:[DI+2]
RIP_SHOULD_LISTEN_in_AX_BX_const_CX_SI_DI_ES name, done
cmp byte ptr ES:[SI+rip_command], RIP_REPLY
jnz done
cmp byte ptr ES:[SI+rip_version], 1
jnz done
add SI, 4
sub CX, 4
process_loop:
sub CX, 20
jb done
cmp word ptr ES:[SI+rip_family], SWAPPED_IP_FAMILY
jnz next
cmp word ptr ES:[SI+rip_zero3], 0 ;; not complete checks
jnz next
cmp word ptr ES:[SI+rip_zero4], 0
jnz next
mov word ptr rip_&name&_data.rip_pack_ctr, CX
mov word ptr rip_&name&_data.rip_pack_ptr, SI
mov CL, byte ptr ES:[SI+rip_metric+3]
mov CH, ROUTE_TRANS
add SI, rip_address
ROUTE_ADD_in_CX_SI_DI_ES_const_DI_ES name
mov CX, word ptr rip_&name&_data.rip_pack_ctr
mov SI, word ptr rip_&name&_data.rip_pack_ptr
next:
add SI, 20
jmp process_loop
done:
ENDM
;;**************************************************************************
;; RIP_SHOULD_LISTEN determines if we should listen to a RIP packet from
;; the gateway AX:BX. If the packet should be dropped this macro jumps
;; to 'drop'
;;
RIP_SHOULD_LISTEN_in_AX_BX_const_CX_SI_DI_ES MACRO name, drop
.errb <drop>
IRP idx, <1,2,3,4,5,6,7,8>
local notme
if idx le rip_&name&_dls
RIP_CHECK_IF_in_AX_BX_const_AX_BX_CX_SI_DI_ES idx, notme
RIP_CHECK_FLAGS idx, ROUTE_DL_DEAF
jz done
jmp drop
notme:
endif
endm
jmp drop
done:
ENDM
RIP_CHECK_FLAGS MACRO dl, mask
test word ptr dl_ip_&dl&_flags, mask
ENDM
;;**************************************************************************
;; RIP_MAKE_PACKET generates a RIP packet in DI:ES for the interface in DL
;; by calling accessing the routing table by means of RIP_NEXT_ENTRY.
;; Thus it assumes SI has been initialized with RIP_INIT_ENTRY or is
;; a continuation of a RIP_NEXT_ENTRY.. CX is set to the length of the
;; packet. It jumps to 'dont_send' when there is not data to send out, so
;; that the packet should not be sent. DH contains the interface metric
;; for the interface. 'if_net' is a pointer to the network part (not assuming
;; subnets, that is based strictly on the Class of the address). This is
;; used to implement the subnet hiding
RIP_MAKE_PACKET_in_DX_SI_DI_out_CX_SI_const_BP MACRO name, if_net, dont_send
local route_loop, packet_full, metric_ok
local send_full_route, not_same_subnet, not_last, send_metric
.errb <dont_send>
;; make up header
mov AL, RIP_REPLY ;; this is a reply packet
stosb
mov AL, 1 ;; version 1
stosb
xor AX, AX ;; must be 0
stosw
dec AX ;; AX now = -1
mov word ptr rip_&name&_data.rip_if_last, AX
mov word ptr rip_&name&_data.rip_if_last+2, AX
mov CX, 4
route_loop:
RIP_NEXT_ENTRY_in_SI_out_SI_const_AX_BX_CX_DX_BP_DI name, packet_full
test [SI+re_flags], ROUTE_SILENT
jnz route_loop
cmp DL, [SI+re_interface] ;; split horizon
jz route_loop
;; this code implements the hiding of subnet info outside
;; the subnet
mov AX, word ptr [SI+re_net] ;; the destination network
mov BX, word ptr [SI+re_net+2]
IP_COMP_NET_in_AX_BX_out_AX_BX_const_CX_DX_BP_SI_DI_ES rip_&name&_dls
cmp AX, word ptr if_net
jnz not_same_subnet
cmp BX, word ptr if_net+2
jz send_full_route
not_same_subnet:
cmp AX, word ptr rip_&name&_data.rip_if_last
jnz not_last
cmp BX, word ptr rip_&name&_data.rip_if_last+2
jz route_loop
not_last:
mov word ptr rip_&name&_data.rip_if_last, AX
mov word ptr rip_&name&_data.rip_if_last+2, BX
mov AX, SWAPPED_IP_FAMILY ;; Address family
stosw
xor AX, AX ;; two bytes of zero
stosw
mov AX, word ptr rip_&name&_data.rip_if_last
stosw
mov AX, word ptr rip_&name&_data.rip_if_last+2
stosw
jmp send_metric
send_full_route:
mov AX, SWAPPED_IP_FAMILY ;; Address family
stosw
xor AX, AX ;; two bytes of zero
stosw
mov AX, word ptr [SI+re_net] ;; the destination network
stosw
mov AX, word ptr [SI+re_net+2]
stosw
send_metric:
xor AX, AX ;; 8 zero bytes
stosw
stosw
stosw
stosw
stosw ;; 4 bytes of metric
mov AH, [SI+re_metric]
add AH, DH ;; add in the interface metric
cmp AH, 16
jbe metric_ok
mov AH, 16
metric_ok:
stosw
add CX, 20
cmp CX, 512-20 ;; is there space for another entry
ja packet_full
jmp route_loop
packet_full:
cmp CX, 4
jz dont_send
ENDM
;;**************************************************************************
;; RIP_INIT_ENTRY initilizes SI so that RIP_NEXT_ENTRY works. When
;; RIP_NEXT_ENTRY is called, SI will point to the first entry of the
;; routing table. This may seem a bit bizarre that RIP_NEXT_ENTRY MUST
;; be called immediately after RIP_INIT_ENTRY, but it actually makes
;; loops easier to construct.
RIP_INIT_ENTRY_out_SI_const_AX_BX_CX_DX_BP_DI MACRO name
.errb <name>
mov SI, offset (rip_&name&_data.rip_table - (size rip_entry))
ENDM
;;**************************************************************************
;; RIP_NEXT_ENTRY gets the next entry in the table and returns it in
;; SI. NOTE: SI must be unchanged from the last RIP_INIT_ENTRY or
;; RIP_NEXT_ENTRY for this function to work. It jumps to 'done' if
;; there are no more entries.
;;
RIP_NEXT_ENTRY_in_SI_out_SI_const_AX_BX_CX_DX_BP_DI MACRO name, done
local search_loop
.errb <done>
search_loop:
add SI, size rip_entry
cmp SI, word ptr rip_&name&_data.rip_table_ptr
ja done
test [SI+re_flags], ROUTE_VALID
jz search_loop
ENDM
;;**************************************************************************
;; RIP_GET_ENTRY gets the table entry for the network in the AX:BX
;; pair and returns a pointer to it in SI. DI returns the pointer to
;; the previous entry in the list (useful for insertions and deletions)
;; It jumps to 'found' if the network entry could is found. If it is
;; not found only DI is valid (SI will be 0)
;;
RIP_GET_ENTRY_in_AX_BX_out_SI_DI_const_AX_BX_CX_BP MACRO name, found
local start, done, next, search_loop
.errb <found>
mov DL, AL
add DL, AH
add DL, BL
add DL, BH ;; compute the hash function
xor DH, DH
mov SI, DX
shl SI, 1
add SI, offset rip_&name&_data.rip_hash
jmp start
search_loop:
cmp AX, word ptr [SI+re_net]
jnz next
cmp BX, word ptr [SI+re_net+2]
jnz next
jmp found
next:
start:
mov DI, SI
mov SI, [SI+re_next]
or SI, SI
jnz search_loop
;; failure
ENDM
;;******************************************************************************
;; Find interface finds the interface for the network address in AX:BX
;; from the dl list 1, 2, ... 'dls' and puts it in CL. It jumps to 'fail'
;; if the address is not on any of the interfaces in 'dls'.
;;
RIP_FIND_IF_in_AX_BX_out_CL_const_AX_BX_SI_DI MACRO dls, fail
local done
.errb <fail>
xor CL, CL
IRP idx, <1,2,3,4,5,6,7,8>
local notme
if idx le dls
RIP_CHECK_IF_in_AX_BX_const_AX_BX_CX_SI_DI_ES idx, notme
mov CL, idx
jmp done
notme:
endif
endm
jmp fail
done:
ENDM
;;*****************************************************************************
;; RIP_CHECK_IF_in_AX_BX checks if the ip address in AX:BX matches the network
;; associated with 'dl'. If it does NOT it jumps to 'fail'
;;
RIP_CHECK_IF_in_AX_BX_const_AX_BX_CX_SI_DI_ES MACRO dl, fail
cmp AX, word ptr dl_ip_&dl&_net
jnz fail
mov DX, BX
and DX, word ptr dl_ip_&dl&_mask+2
cmp DX, word ptr dl_ip_&dl&_net+2
jnz fail
ENDM
;;*****************************************************************************
;; RIP_LOG_ROUTE_in_SI_const_ALL prints out the route entry pointed to by
;; SI:DS to the loging host
;;
RIP_LOG_ROUTE_in_SI_const_all MACRO name, severity
push AX
push BX
mov AX, word ptr [SI+re_net]
mov BX, word ptr [SI+re_net+2]
LOG_PRINT_INET_in_AX_BX %mylog, L_ROUTE, severity, < Routing to network >
xor AX, AX
mov AL, byte ptr [SI+re_interface]
LOG_PRINT_REG_DEC %mylog, L_ROUTE, severity, < Through interface >, AX
mov AX, word ptr [SI+re_gateway]
xchg AH, AL
LOG_PRINT_REG_HEX %mylog, L_ROUTE, severity, < To host with IP addr ending with >, AX
xor AX, AX
mov AL, byte ptr [SI+re_metric]
LOG_PRINT_REG_DEC %mylog, L_ROUTE, severity, < Routing Metric >, AX
pop BX
pop AX
ENDM