static char rcsident
[] = "$Header: ip_input.c,v 1.39 85/07/31 09:31:26 walsh Exp $";
#include "../h/socketvar.h"
#include "../h/protosw.h"
#include "../net/route.h"
#include "../net/raw_cb.h"
#include "../bbnnet/in.h"
#include "../bbnnet/net.h"
#include "../bbnnet/in_pcb.h"
#include "../bbnnet/in_var.h"
#include "../bbnnet/ip.h"
#include "../bbnnet/icmp.h"
#include "../bbnnet/hmp_traps.h"
static char rcsiphdr
[] = RCSIPHDR
;
#define rawreaders(r) ((r)->rcb_next != (r))
#define any_rawreaders() rawreaders(&rawcb)
#define FIELD_OFF(fld, type) (((char *) (&(((type *) 0)->fld))) - ((char *) 0))
struct in_stat otherstat
;
struct ipq
*n_ip_head
; /* -> top of ip reass. queue */
struct ipq
*n_ip_tail
; /* -> end of ip reass. queue */
* The protocol layers above IP assume the IP header and the protocol
* header are contiguous. However, need to save the options in case
* a connection oriented protocol (RDP/TCP) wants to respond to an
* incoming packet (SYN) over the same route if the packet got here
* using IP source routing. This allows connection establishment and
* maintenance when the remote end is on a network that is not known
* to our gateways. Only applicable options are Loose/Strict source
* routing, so rather than saving them in ip_stripopt() and reinterpreting
* them, we'll set up something specific and appropriate here.
struct in_addr ip_hops
[MAX_IPOPTLEN
/ sizeof(struct in_addr
)];
/* use a dispatch table for protocol dispatching */
struct ipswitch ipsw
[IPPROTO_MAX
];
extern struct ip
*ip_reass();
register struct protosw
*pr
;
pr
= pffindproto(PF_INET
, IPPROTO_IP
, SOCK_RAW
);
/* build our internal switch table */
for(i
=0; i
< IPPROTO_MAX
; i
++)
ipsw
[i
].ipsw_hlen
= sizeof(struct ip
);
for(pr
= inetdomain
.dom_protosw
; pr
< inetdomain
.dom_protoswNPROTOSW
; pr
++)
if (pr
->pr_protocol
>= IPPROTO_MAX
)
if (pr
->pr_type
== SOCK_RAW
)
ipsw
[pr
->pr_protocol
].ipsw_raw
= pr
;
ipsw
[pr
->pr_protocol
].ipsw_user
= pr
;
ipintrq
.ifq_maxlen
= IFQ_MAXLEN
; /* got a better number? */
* Being global might be a little gross, but we just got the interface pointer
* passed up the week before 4.3 release.
* IP network software interrupt service routine. Dequeue a message from the
* ip input queue, and pass to internal ip input processor (ip_input).
* Half-hearted attempt to prevent recursive panics due to network
* bugs since panic calls boot, which lowers spl. Note that the
* drivers will still store packets in mbufs and that some processing
* (ARP, Chaosnet) that occurs at splimp() will still proceed. At
* least this should preserve state information for post-mortem.
/* for 4.3, Berkeley finally changed imp code to queue up messages
* for ctlinput path, so don't need to check for them here with our
IF_DEQUEUEIF (&ipintrq
, m
, inetifp
);
* Called from local net level upon recpt of an internet datagram or fragment.
* This routine does fragment reassembly, if necessary, and passes completed
* datagrams to higher level protocol processing routines on the basis of the
* ip header protocol field. It is passed a pointer to an mbuf chain
* containing the datagram/fragment. The mbuf offset+length are set to point
register struct mbuf
*mp
;
* make sure dtom() macro works by getting header out of mbufs using
* pages, also make sure header contiguous in first mbuf.
if ((mp
->m_off
> MMAXOFF
) || (mp
->m_len
< sizeof(struct ip
)))
* Might as well avoid doing m_pullup twice.
* Common case is using cluster for large chunk of data.
if (mp
->m_len
< FIELD_OFF(ip_p
, struct ip
) + sizeof(ip
->ip_p
))
ip
= mtod(mp
, struct ip
*);
i
= ipsw
[ip
->ip_p
].ipsw_hlen
;
if ((mp
= m_pullup(mp
, i
)) == NULL
)
ip
= mtod(mp
, struct ip
*);
* make sure header does not overflow mbuf (is contiguous)
hlen
= ip
->ip_hl
<< IP_HLSHIFT
;
if ((mp
= m_pullup(mp
, hlen
)) == NULL
)
ip_log(ip
, "ip header overflow");
/* hmp_trap(T_IP_OVFLO, (caddr_t)0,0); */
/* netlog(mp); no longer have mbuf list */
ip
= mtod(mp
, struct ip
*);
* Adjust msg length to remove any driver padding. Make sure that
* message length matches ip length.
for (i
= 0, m
= mp
; m
->m_next
!= NULL
; m
= m
->m_next
)
i
-= ntohs((u_short
)ip
->ip_len
) - m
->m_len
;
ip_log(ip
, "truncated ip packet");
/* hmp_trap(T_IP_TRUNC, (caddr_t)0, 0); */
/* used to do inline cksum here via sed */
if (i
!= (j
= (u_short
)in_cksum(dtom(ip
), hlen
)))
/* hmp_trap(T_IP_CKSUM, (caddr_t)0,0); */
inet_cksum_err ("ip", ip
, (u_long
) i
, (u_long
) j
);
ic_errmsg (icmp_addr(ip
), ip
->ip_src
,
ICMP_PARM
, 0, FIELD_OFF(ip_sum
, struct ip
),
hlen
+ ICMP_ERRLEN
, (char *) ip
);
* Make sure this packet is addressed to us before we put fields
* in host order and do reassembly (fragments may reach destination
* Remember that IP source routing option can change ip_dst, so have
* to do option processing for all incoming packets with options.
* Even if the packet turns out to be for us and we strip them.
fwdinp
.inp_route
.ro_rt
= NULL
; /* in case fwd, but no options */
ip_nhops
= 0; /* for source routed TCP/RDP... connections */
if (hlen
> sizeof(struct ip
))
* Any route allocated by ip_opt() 1. will be used by
* ip_forward(), and 2. will only be needed by ip_forward()
if (! ip_opt (ip
, hlen
, &fwdinp
))
ip_log (ip
, "ip option error");
* if packet is not for us then forward
if ((ia
= in_iawithaddr(ip
->ip_dst
, TRUE
)) == NULL
)
ip_forward (&fwdinp
, ip
, mp
, hlen
);
ip
->ip_len
= ntohs((u_short
)ip
->ip_len
);
if ((int)(ip
->ip_len
-= hlen
) < 0)
ip_log(ip
, "ip header length error");
/* hmp_trap(T_IP_HLEN, (caddr_t)0,0); */
ip
->ip_off
= ntohs((u_short
)ip
->ip_off
);
ip
->ip_mff
= ((ip
->ip_off
& ip_mf
) ? TRUE
: FALSE
);
ip
->ip_off
<<= IP_OFFSHIFT
;
/* look for chain on reassembly queue with this header */
for (fp
= ipfrags
.n_ip_head
; (fp
!= NULL
&& (
ip
->ip_src
.s_addr
!= fp
->iqh
.ip_src
.s_addr
||
ip
->ip_dst
.s_addr
!= fp
->iqh
.ip_dst
.s_addr
||
ip
->ip_id
!= fp
->iqh
.ip_id
||
ip
->ip_p
!= fp
->iqh
.ip_p
)); fp
= fp
->iq_next
);
if (!ip
->ip_mff
&& ip
->ip_off
== 0)
{ /* free existing reass chain */
q
= fp
->iqx
.ip_next
; /* free mbufs assoc. w/chain */
while (q
!= (struct ip
*)fp
)
ip_freef(fp
); /* free header */
* The options aren't of any use to higher level
* protocols or of any concern to the user process.
if (hlen
> sizeof(struct ip
))
ip
= ip_reass(ip
, fp
, &fragsize
);
/* call next level with completed datagram */
/* if (rawreaders((struct rawcb *)ipsw[ip->ip_p].ipsw_raw->pr_ppcbq)) */
if (m
= m_copy(mp
, 0, M_COPYALL
))
ipsw
[ip
->ip_p
].ipsw_raw
->pr_input(m
);
if (ip
->ip_p
== IPPROTO_TCP
)
/* wish I didn't need this special case for fragsize */
else if (ipsw
[ip
->ip_p
].ipsw_user
!= 0)
/* There's a protocol implementation for these packets */
ipsw
[ip
->ip_p
].ipsw_user
->pr_input(mp
);
else if (ip
->ip_p
== IPPROTO_ICMP
)
* Since don't want user to get a non-raw ICMP socket, did not make
* an entry in the protocol jump table; also wanted to be able to
* Don't bother everyone on the net, and remember some other
* host may support the protocol.
if ((!in_broadcast(ip
->ip_src
)) && (!in_broadcast(ip
->ip_dst
)))
ic_errmsg (icmp_addr(ip
), ip
->ip_src
,
ICMP_UNRCH
, ICMP_UNRCH_PR
, FIELD_OFF(ip_p
, struct ip
),
sizeof(struct ip
) + ICMP_ERRLEN
, (char *) ip
);
/* get rid of the packet */
* We've received an IP fragment. Try to perform IP reassembly.
struct ip
*ip_reass(ip
, fp
, fragsize
)
register struct ip
*q
, *savq
;
register struct mbuf
*mp
;
hlen
= ip
->ip_hl
<< IP_HLSHIFT
;
* Only the first fragment retains the IP header.
* This is the first fragment of the IP datagram that we've
* received. Set up reassembly q header.
if ((m
= m_get(M_WAIT
, MT_FTABLE
)) == NULL
)
fp
= mtod(m
, struct ipq
*);
fp
->iqx
.ip_next
= fp
->iqx
.ip_prev
= (struct ip
*)fp
;
bcopy((caddr_t
)ip
, (caddr_t
)&fp
->iqh
, sizeof(struct ip
));
* and enter this into the list of fragmented IP datagrams
fp
->iq_prev
= ipfrags
.n_ip_tail
;
if (ipfrags
.n_ip_head
!= NULL
)
ipfrags
.n_ip_tail
->iq_next
= fp
;
* Merge fragment into reass.q
* Algorithm: Match start and end bytes of new
* fragment with fragments on the queue. If no
* overlaps are found, add new frag. to the queue.
* Otherwise, adjust start and end of new frag. so no
* overlap and add remainder to queue. If any
* fragments are completely covered by the new one, or
* if the new one is completely duplicated, free the
q
= fp
->iqx
.ip_next
; /* -> top of reass. chain */
ip
->ip_end
= ip
->ip_off
+ ip
->ip_len
- 1;
/* record the maximum fragment size for TCP */
fp
->iq_size
= MAX(ip
->ip_len
, fp
->iq_size
);
/* skip frags which new doesn't overlap at end */
while ((q
!= (struct ip
*)fp
) && (ip
->ip_off
> q
->ip_end
))
if (q
== (struct ip
*)fp
)
{ /* frag at end of chain */
ip_enq(ip
, fp
->iqx
.ip_prev
);
{ /* frag doesn't overlap any */
if (ip
->ip_end
< q
->ip_off
)
/* new overlaps beginning of next frag only */
else if (ip
->ip_end
< q
->ip_end
)
if ((i
= ip
->ip_end
-q
->ip_off
+1) < ip
->ip_len
)
/* new overlaps end of previous frag */
if (ip
->ip_off
<= q
->ip_off
)
if ((i
= q
->ip_end
-ip
->ip_off
+1)
/* new overlaps at beginning of successor frags */
while ((q
!= (struct ip
*)fp
) &&
(q
->ip_off
<= ip
->ip_end
))
if (q
->ip_end
<= ip
->ip_end
)
if ((i
= ip
->ip_end
-q
->ip_off
+1) < ip
->ip_len
)
/* enqueue whatever is left of new before successors */
/* check for completed fragment reassembly */
if ((i
= ip_done(&fp
->iqx
)) == 0)
ip
= fp
->iqx
.ip_next
; /* -> top mbuf */
ip
->ip_len
= i
; /* total data length */
*fragsize
= fp
->iq_size
; /* remember for TCP */
if ((hlen
= ip
->ip_hl
<<IP_HLSHIFT
) > sizeof(struct ip
))
ip_mergef(&fp
->iqx
); /* clean frag chain */
/* copy src/dst internet address to header mbuf */
ip
->ip_src
= fp
->iqh
.ip_src
;
ip
->ip_dst
= fp
->iqh
.ip_dst
;
ip_freef(fp
); /* dequeue header */
* Let people control gateway action by patching this:
int ip_forwarding
= FALSE
;
* Try to forward the packet. Act like a gateway.
ip_forward (fwdinp
, ip
, mp
, hlen
)
register struct mbuf
*mcopy
;
register unsigned icmplen
;
* Also copy forwarded packets, just like copy TCP/UDP/RDP...
* packets sent to us so that can debug gateway action problems.
* It's easy enough for the user-level program to filter these
if (mcopy
= m_copy(mp
, 0, M_COPYALL
))
icmplen
= MIN(len
, hlen
+ ICMP_ERRLEN
);
if (ip_forwarding
&& ip
->ip_ttl
)
* Save chunk of ip packet in case there is an error
mcopy
= m_copy (mp
, 0, (int)icmplen
);
* The packet is not sourced by the local machine, so
* save ourselves a useless call to ip_route in ip_send().
* Also, don't want ip_send to work if sending from
* 8.7.0.2 to 192.1.11.1 via 8.0.0.16, and 8.0.0.16
* knows about a default gateway on net 8. This can
* cause an ENETUNREACH if 192.1.11 is not a network
* that default gateway knows about. [8.0.0.16 is on
* 192.1.11 and ip_route uses default gateway on net 8
* while rtalloc uses local interface]
* This route should be found by rtalloc, so let's do
if (fwdinp
->inp_route
.ro_rt
== NULL
)
/* Isn't a source routed packet */
bzero ((caddr_t
) fwdinp
, sizeof(*fwdinp
));
sin
= (struct sockaddr_in
*) &fwdinp
->inp_route
.ro_dst
;
sin
->sin_family
= AF_INET
;
sin
->sin_addr
= ip
->ip_dst
;
rtalloc (&fwdinp
->inp_route
);
* Check to see if should send ICMP redirect. Don't
* send redirect if source routing was used.
if ((rt
= fwdinp
->inp_route
.ro_rt
) != NULL
)
sin
= (struct sockaddr_in
*) &rt
->rt_gateway
;
if (! (rt
->rt_flags
& (RTF_GATEWAY
|RTF_HOST
)))
send_redirect (ip
, ip
->ip_dst
, ICMP_REDIR_HOST
, icmplen
);
else if (iptonet(ip
->ip_src
) == iptonet(sin
->sin_addr
))
send_redirect (ip
, sin
->sin_addr
, ICMP_REDIR_NET
, icmplen
);
if (fwdinp
->inp_route
.ro_rt
== NULL
)
/* no way to get there from here */
error
= ip_send (fwdinp
, mp
, (int)len
, TRUE
);
rtfree(fwdinp
->inp_route
.ro_rt
);
fwdinp
->inp_route
.ro_rt
= NULL
;
log(KERN_RECOV
, "ip_forward: error %d\n", error
);
if (fwdinp
->inp_route
.ro_rt
)
/* was source routed by IP option */
rtfree (fwdinp
->inp_route
.ro_rt
);
fwdinp
->inp_route
.ro_rt
= NULL
;
ip
= mtod(mcopy
, struct ip
*);
ic_errmsg (redir_addr(ip
), ip
->ip_src
, type
, code
, 0, icmplen
, (char *) ip
);
/* hmp_trap(T_IP_ADDRS, (caddr_t) 0, 0); */
* If not acting as a gateway, don't want some one else's
* misconception to flood our console or logfile. This error
* can be found through netstat an ip_drops.
ip_log(ip
, "ip forwarding error");
* Check to see if fragment reassembly is complete
while ((q
!= p
) && (q
->ip_off
== next
));
if ((q
== p
) && !(q
->ip_prev
->ip_mff
)) /* all fragments in */
return(next
); /* total data length */
* Merge mbufs of fragments of completed datagram
register struct mbuf
*m
, *n
;
q
= p
->ip_next
; /* -> bottom of reass chain */
n
= (struct mbuf
*)&dummy
; /* dummy for init assignment */
* If free mbuf holding q, cannot access q->ip_next in case
* that mbuf is used by device code for an incoming packet.
register struct ip
*next
;
else /* free null mbufs */
n
->m_next
= m
= m_free(m
);
* Dequeue and free reass.q header
(fp
->iq_prev
)->iq_next
= fp
->iq_next
;
ipfrags
.n_ip_head
= fp
->iq_next
;
(fp
->iq_next
)->iq_prev
= fp
->iq_prev
;
ipfrags
.n_ip_tail
= fp
->iq_prev
;
if ((optlen
= (hlen
- sizeof(struct ip
))) > 0)
caddr_t end_of_ip
, end_of_opt
;
end_of_ip
= (char *) (ip
+1);
end_of_opt
= end_of_ip
+ optlen
;
bcopy (end_of_opt
, end_of_ip
, len
);
* FALSE -> options were in error, and an icmp message has been sent
#define MIN_OFF 4 /* since option is a 1, not 0, based array */
* Record route in same form as ip_setopt()
/* Use both loose and strict source routing? */
log(KERN_RECOV
, "ip_nhops %d\n", ip_nhops
);
if (olen
> sizeof(ip_hops
))
log(KERN_RECOV
, "save_rte: olen %d\n", olen
);
off
= option
[OFF_OFFSET
];
p
= (struct in_addr
*) (&option
[off
- 1]);
q
= (struct in_addr
*) (&option
[MIN_OFF
-1]);
x
[1] = option
[0]; /* loose/strict source routing */
x
[2] = (p
- q
) * sizeof(struct in_addr
) + 3;
ip_nhops
++; /* = 1 (1 long for opt hdr) */
p
--; /* p points at first hop for return route */
ip_hops
[p
-q
+2] = (*p
); /* save first hop after return route option */
p
--; /* it is in what will be ip_hops[ip_nhops] */
/* record return path as an IP source route */
ip_hops
[ip_nhops
] = (*p
);
/* remember eventual destination is in the option field */
ip_opt (ip
, hlen
, fwdinp
)
endopt
= ((u_char
*) ip
) + hlen
;
option
= (u_char
*) (ip
+1);
/* so much for security */
off
= option
[OFF_OFFSET
];
code
= &option
[OFF_OFFSET
] - ((u_char
*) ip
);
off
--; /* adjust for use by C */
if (in_iawithaddr(ip
->ip_dst
, TRUE
) == NULL
)
* With loose routing, may take a few hops
* to get to current nexthop.
if (off
> (olen
- sizeof(struct in_addr
)))
/* hints all used up. pkt for us */
save_rte (option
, ip
->ip_src
);
nexthop
= *((struct in_addr
*) (option
+ off
));
* record outgoing interface
if (! ip_opt_route(fwdinp
, nexthop
))
option
[OFF_OFFSET
] += sizeof(struct in_addr
);
if (fwdinp
->inp_route
.ro_rt
== NULL
)
* Destined for ourselves, and we're
* just going to strip the options off
*((struct in_addr
*) (option
+ off
)) =
IA_INADDR(in_iafromif (fwdinp
->inp_route
.ro_rt
->rt_ifp
));
off
= option
[OFF_OFFSET
];
code
= &option
[OFF_OFFSET
] - ((u_char
*) ip
);
off
--; /* adjust for use by C */
if (in_iawithaddr(ip
->ip_dst
, TRUE
) == NULL
)
/* strict path -> someone goofed */
/* should have come in on us for us */
if (off
> (olen
- sizeof(struct in_addr
)))
save_rte (option
, ip
->ip_src
);
nexthop
= *((struct in_addr
*) (option
+ off
));
if ((ia
= in_iawithnet(nexthop
)) == NULL
)
/* strict path -> someone goofed
* we should be directly connected to
*((struct in_addr
*) (option
+ off
)) = IA_INADDR(ia
);
option
[OFF_OFFSET
] += sizeof(struct in_addr
);
off
= option
[OFF_OFFSET
];
code
= &option
[OFF_OFFSET
] - ((u_char
*) ip
);
off
--; /* adjust for use by C */
if (off
> (olen
- sizeof(u_long
)))
/* increment overflow count */
if ((option
[3] & 0xf0) == 0xf0)
/* overflow overflowed */
code
= &option
[OFF_OLEN
] - ((u_char
*) ip
);
/* want specific host to stamp */
ia
= in_iawithaddr(*((struct in_addr
*) (option
+ off
)), FALSE
);
/* record stamping host */
*((struct in_addr
*) (option
+ off
)) = IA_INADDR (ia
);
off
+= sizeof(struct in_addr
);
option
[OFF_OFFSET
] += sizeof(struct in_addr
);
if (off
> (olen
- sizeof(u_long
)))
*((u_long
*) (option
+ off
)) = iptime();
option
[OFF_OFFSET
] += sizeof(u_long
);
off
= option
[OFF_OFFSET
];
code
= &option
[OFF_OFFSET
] - ((u_char
*) ip
);
off
--; /* adjust for use by C */
if (off
> (olen
- sizeof(u_long
)))
/* no space left for recording route */
/* record outgoing interface */
if (! ip_opt_route(fwdinp
, ip
->ip_dst
))
option
[OFF_OFFSET
] += sizeof(struct in_addr
);
if (fwdinp
->inp_route
.ro_rt
== NULL
)
* Destined for us, and we're just
* going to strip options off
*((struct in_addr
*) (option
+ off
)) =
IA_INADDR(in_iafromif (fwdinp
->inp_route
.ro_rt
->rt_ifp
));
ic_errmsg (icmp_addr(ip
), ip
->ip_src
,
type
, code
, option
- ((u_char
*) ip
), hlen
, (char *) ip
);
ip_opt_route (fwdinp
, dst
)
register struct inpcb
*fwdinp
;
register struct sockaddr_in
*sin
;
/* in case they use several options involving routing */
if (fwdinp
->inp_route
.ro_rt
)
bzero ((caddr_t
) fwdinp
, sizeof(*fwdinp
));
sin
= (struct sockaddr_in
*) &fwdinp
->inp_route
.ro_dst
;
/* not sure ip_send cares about this stuff ... */
sin
->sin_family
= AF_INET
;
/* Don't allocate route if not forwarding packet.
* This saves us from doing a check in ip_input() to see
* if we should do a rtfree() for an uncommon occurrence.
if (in_iawithaddr(dst
, TRUE
) != NULL
)
rtalloc (&fwdinp
->inp_route
);
return (fwdinp
->inp_route
.ro_rt
!= NULL
);
* IP fragment reassembly timeout routine.
register struct ip
*p
, *q
;
register struct ipq
*fp
, *next
;
if (timflag
= !timflag
) /* looks strange, doesn't it? */
/* search through reass.q */
for (fp
= ipfrags
.n_ip_head
; fp
!= NULL
; fp
= next
)
* If fragment times out, mbufs are freed, and can't
* use next pointer since mbuf may be grabbed by
if (--(fp
->iqx
.ip_ttl
) == 0)
q
= fp
->iqx
.ip_next
; /* free mbufs assoc. w/chain */
while (q
!= (struct ip
*)fp
)
/* ### generate timed out in reassembly msg */
/* hmp_trap(T_IP_FDROP, (caddr_t)0,0); */
ip_freef(fp
); /* free header */
* Called at splimp from uipc_mbuf.c
* Network code needs to free up space! IP fragments dropped.
register struct ip
*p
, *q
;
while (fp
= ipfrags
.n_ip_head
)
q
= fp
->iqx
.ip_next
; /* free mbufs assoc w/chain */
while (q
!= (struct ip
*)fp
)
ip_freef(fp
); /* free header */
inet_cksum_err (protoname
, ip
, was
, should_be
)
union { u_long ul
; u_char c
[4]; } s
, d
;
s
.ul
= ip
->ip_src
.s_addr
;
d
.ul
= ip
->ip_dst
.s_addr
;
"%s checksum was 0x%x not 0x%x src %d.%d.%d.%d dst %d.%d.%d.%d\n",
protoname
, was
, should_be
,
s
.c
[0], s
.c
[1], s
.c
[2], s
.c
[3],
d
.c
[0], d
.c
[1], d
.c
[2], d
.c
[3]);
union { u_long ul
; u_char c
[4]; } s
, d
;
s
.ul
= ip
->ip_src
.s_addr
;
d
.ul
= ip
->ip_dst
.s_addr
;
log(KERN_RECOV
, "%s: src %d.%d.%d.%d dst %d.%d.%d.%d\n",
s
.c
[0], s
.c
[1], s
.c
[2], s
.c
[3],
d
.c
[0], d
.c
[1], d
.c
[2], d
.c
[3]);
union { u_long ul
; u_char c
[4]; } f
, t
;
log(KERN_RECOV
, "%s: no route %d.%d.%d.%d -> %d.%d.%d.%d\n",
f
.c
[0], f
.c
[1], f
.c
[2], f
.c
[3],
t
.c
[0], t
.c
[1], t
.c
[2], t
.c
[3]);