2 * DEBUG: section 12 Internet Cache Protocol (ICP)
3 * AUTHOR: Duane Wessels
5 * SQUID Web Proxy Cache http://www.squid-cache.org/
6 * ----------------------------------------------------------
8 * Squid is the result of efforts by numerous individuals from
9 * the Internet community; see the CONTRIBUTORS file for full
10 * details. Many organizations have provided support for Squid's
11 * development; see the SPONSORS file for full details. Squid is
12 * Copyrighted (C) 2001 by the Regents of the University of
13 * California; see the COPYRIGHT file for full details. Squid
14 * incorporates software developed and/or copyrighted by other
15 * sources; see the CREDITS file for full details.
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
34 \defgroup ServerProtocolICPInternal2 ICPv2 Internals
35 \ingroup ServerProtocolICPAPI
42 #include "HttpRequest.h"
43 #include "acl/FilledChecklist.h"
45 #include "AccessLogEntry.h"
47 #include "SquidTime.h"
49 #include "icmp/net_db.h"
50 #include "ip/IpAddress.h"
52 /// \ingroup ServerProtocolICPInternal2
53 static void icpLogIcp(const IpAddress
&, log_type
, int, const char *, int);
55 /// \ingroup ServerProtocolICPInternal2
56 static void icpHandleIcpV2(int, IpAddress
&, char *, int);
58 /// \ingroup ServerProtocolICPInternal2
59 static void icpCount(void *, int, size_t, int);
62 \ingroup ServerProtocolICPInternal2
63 * IcpQueueHead is global so comm_incoming() knows whether or not
64 * to call icpUdpSendQueue.
66 static icpUdpData
*IcpQueueHead
= NULL
;
67 /// \ingroup ServerProtocolICPInternal2
68 static icpUdpData
*IcpQueueTail
= NULL
;
70 /// \ingroup ServerProtocolICPInternal2
71 IpAddress theOutICPAddr
;
74 _icp_common_t::_icp_common_t() : opcode(ICP_INVALID
), version(0), length(0), reqnum(0), flags(0), pad(0), shostid(0)
77 _icp_common_t::_icp_common_t(char *buf
, unsigned int len
)
79 if (len
< sizeof(_icp_common_t
)) {
85 xmemcpy(this, buf
, sizeof(icp_common_t
));
87 * Convert network order sensitive fields
89 length
= ntohs(length
);
90 reqnum
= ntohl(reqnum
);
96 _icp_common_t::getOpCode() const
98 if (opcode
> (char)ICP_END
)
101 return (icp_opcode
)opcode
;
106 ICPState::ICPState(icp_common_t
&aHeader
, HttpRequest
*aRequest
):
108 request(HTTPMSGLOCK(aRequest
)),
113 ICPState::~ICPState()
116 HTTPMSGUNLOCK(request
);
124 /// \ingroup ServerProtocolICPInternal2
125 class ICP2State
: public ICPState
, public StoreClient
129 ICP2State(icp_common_t
& aHeader
, HttpRequest
*aRequest
):
130 ICPState(aHeader
, aRequest
),rtt(0),src_rtt(0),flags(0) {}
133 void created(StoreEntry
* newEntry
);
140 ICP2State::~ICP2State()
144 ICP2State::created(StoreEntry
*newEntry
)
146 StoreEntry
*entry
= newEntry
->isNull () ? NULL
: newEntry
;
147 debugs(12, 5, "icpHandleIcpV2: OPCODE " << icp_opcode_str
[header
.opcode
]);
148 icp_opcode codeToSend
;
150 if (icpCheckUdpHit(entry
, request
)) {
151 codeToSend
= ICP_HIT
;
154 if (Config
.onoff
.test_reachability
&& rtt
== 0) {
155 if ((rtt
= netdbHostRtt(request
->GetHost())) == 0)
156 netdbPingSite(request
->GetHost());
158 #endif /* USE_ICMP */
160 if (icpGetCommonOpcode() != ICP_ERR
)
161 codeToSend
= icpGetCommonOpcode();
162 else if (Config
.onoff
.test_reachability
&& rtt
== 0)
163 codeToSend
= ICP_MISS_NOFETCH
;
165 codeToSend
= ICP_MISS
;
168 icpCreateAndSend(codeToSend
, flags
, url
, header
.reqnum
, src_rtt
, fd
, from
);
174 /// \ingroup ServerProtocolICPInternal2
176 icpLogIcp(const IpAddress
&caddr
, log_type logcode
, int len
, const char *url
, int delay
)
180 if (LOG_TAG_NONE
== logcode
)
183 if (LOG_ICP_QUERY
== logcode
)
186 clientdbUpdate(caddr
, logcode
, PROTO_ICP
, len
);
188 if (!Config
.onoff
.log_udp
)
191 al
.icp
.opcode
= ICP_QUERY
;
195 al
.cache
.caddr
= caddr
;
197 al
.cache
.replySize
= len
;
199 al
.cache
.code
= logcode
;
201 al
.cache
.msec
= delay
;
203 accessLogLog(&al
, NULL
);
206 /// \ingroup ServerProtocolICPInternal2
208 icpUdpSendQueue(int fd
, void *unused
)
214 while ((q
= IcpQueueHead
) != NULL
) {
215 delay
= tvSubUsec(q
->queue_time
, current_time
);
216 /* increment delay to prevent looping */
217 x
= icpUdpSend(fd
, q
->address
, (icp_common_t
*) q
->msg
, q
->logcode
, ++delay
);
218 IcpQueueHead
= q
->next
;
227 _icp_common_t::createMessage(
235 icp_common_t
*headerp
= NULL
;
236 char *urloffset
= NULL
;
238 buf_len
= sizeof(icp_common_t
) + strlen(url
) + 1;
240 if (opcode
== ICP_QUERY
)
241 buf_len
+= sizeof(u_int32_t
);
243 buf
= (char *) xcalloc(buf_len
, 1);
245 headerp
= (icp_common_t
*) (void *) buf
;
247 headerp
->opcode
= (char) opcode
;
249 headerp
->version
= ICP_VERSION_CURRENT
;
251 headerp
->length
= (u_int16_t
) htons(buf_len
);
253 headerp
->reqnum
= htonl(reqnum
);
255 headerp
->flags
= htonl(flags
);
257 headerp
->pad
= htonl(pad
);
259 theOutICPAddr
.GetInAddr( *((struct in_addr
*)&headerp
->shostid
) );
261 urloffset
= buf
+ sizeof(icp_common_t
);
263 if (opcode
== ICP_QUERY
)
264 urloffset
+= sizeof(u_int32_t
);
266 xmemcpy(urloffset
, url
, strlen(url
));
268 return (icp_common_t
*)buf
;
281 len
= (int) ntohs(msg
->length
);
282 debugs(12, 5, "icpUdpSend: FD " << fd
<< " sending " <<
283 icp_opcode_str
[msg
->opcode
] << ", " << len
<< " bytes to " << to
);
285 x
= comm_udp_sendto(fd
, to
, msg
, len
);
288 /* successfully written */
289 icpLogIcp(to
, logcode
, len
, (char *) (msg
+ 1), delay
);
290 icpCount(msg
, SENT
, (size_t) len
, delay
);
292 } else if (0 == delay
) {
293 /* send failed, but queue it */
294 queue
= (icpUdpData
*) xcalloc(1, sizeof(icpUdpData
));
297 queue
->len
= (int) ntohs(msg
->length
);
298 queue
->queue_time
= current_time
;
299 queue
->logcode
= logcode
;
301 if (IcpQueueHead
== NULL
) {
302 IcpQueueHead
= queue
;
303 IcpQueueTail
= queue
;
304 } else if (IcpQueueTail
== IcpQueueHead
) {
305 IcpQueueTail
= queue
;
306 IcpQueueHead
->next
= queue
;
308 IcpQueueTail
->next
= queue
;
309 IcpQueueTail
= queue
;
312 commSetSelect(fd
, COMM_SELECT_WRITE
, icpUdpSendQueue
, NULL
, 0);
313 statCounter
.icp
.replies_queued
++;
316 statCounter
.icp
.replies_dropped
++;
323 icpCheckUdpHit(StoreEntry
* e
, HttpRequest
* request
)
328 if (!e
->validToSend())
331 if (Config
.onoff
.icp_hit_stale
)
334 if (refreshCheckICP(e
, request
))
341 * This routine selects an ICP opcode for ICP misses.
343 \retval ICP_ERR no opcode selected here
344 \retval ICP_MISS_NOFETCH store is rebuilding, no fetch is possible yet
349 /* if store is rebuilding, return a UDP_MISS_NOFETCH */
351 if ((StoreController::store_dirs_rebuilding
&& opt_reload_hit_only
) ||
352 hit_only_mode_until
> squid_curtime
) {
353 return ICP_MISS_NOFETCH
;
360 icpLogFromICPCode(icp_opcode opcode
)
362 if (opcode
== ICP_ERR
)
363 return LOG_UDP_INVALID
;
365 if (opcode
== ICP_DENIED
)
366 return LOG_UDP_DENIED
;
368 if (opcode
== ICP_HIT
)
371 if (opcode
== ICP_MISS
)
374 if (opcode
== ICP_MISS_NOFETCH
)
375 return LOG_UDP_MISS_NOFETCH
;
377 fatal("expected ICP opcode\n");
379 return LOG_UDP_INVALID
;
383 icpCreateAndSend(icp_opcode opcode
, int flags
, char const *url
, int reqnum
, int pad
, int fd
, const IpAddress
&from
)
385 icp_common_t
*reply
= _icp_common_t::createMessage(opcode
, flags
, url
, reqnum
, pad
);
386 icpUdpSend(fd
, from
, reply
, icpLogFromICPCode(opcode
), 0);
390 icpDenyAccess(IpAddress
&from
, char *url
, int reqnum
, int fd
)
392 debugs(12, 2, "icpDenyAccess: Access Denied for " << from
<< " by " << AclMatchedName
<< ".");
394 if (clientdbCutoffDenied(from
)) {
396 * count this DENIED query in the clientdb, even though
397 * we're not sending an ICP reply...
399 clientdbUpdate(from
, LOG_UDP_DENIED
, PROTO_ICP
, 0);
401 icpCreateAndSend(ICP_DENIED
, 0, url
, reqnum
, 0, fd
, from
);
406 icpAccessAllowed(IpAddress
&from
, HttpRequest
* icp_request
)
408 /* absent an explicit allow, we deny all */
409 if (!Config
.accessList
.icp
)
412 ACLFilledChecklist
checklist(Config
.accessList
.icp
, icp_request
, NULL
);
413 checklist
.src_addr
= from
;
414 checklist
.my_addr
.SetNoAddr();
415 int result
= checklist
.fastCheck();
420 icpGetUrlToSend(char *url
)
422 if (strpbrk(url
, w_space
))
423 return rfc1738_escape(url
);
429 icpGetRequest(char *url
, int reqnum
, int fd
, IpAddress
&from
)
431 if (strpbrk(url
, w_space
)) {
432 url
= rfc1738_escape(url
);
433 icpCreateAndSend(ICP_ERR
, 0, rfc1738_escape(url
), reqnum
, 0, fd
, from
);
439 if ((result
= HttpRequest::CreateFromUrl(url
)) == NULL
)
440 icpCreateAndSend(ICP_ERR
, 0, url
, reqnum
, 0, fd
, from
);
447 doV2Query(int fd
, IpAddress
&from
, char *buf
, icp_common_t header
)
452 /* We have a valid packet */
453 char *url
= buf
+ sizeof(icp_common_t
) + sizeof(u_int32_t
);
454 HttpRequest
*icp_request
= icpGetRequest(url
, header
.reqnum
, fd
, from
);
459 HTTPMSGLOCK(icp_request
);
461 if (!icpAccessAllowed(from
, icp_request
)) {
462 icpDenyAccess(from
, url
, header
.reqnum
, fd
);
463 HTTPMSGUNLOCK(icp_request
);
467 if (header
.flags
& ICP_FLAG_SRC_RTT
) {
468 rtt
= netdbHostRtt(icp_request
->GetHost());
469 int hops
= netdbHostHops(icp_request
->GetHost());
470 src_rtt
= ((hops
& 0xFFFF) << 16) | (rtt
& 0xFFFF);
473 flags
|= ICP_FLAG_SRC_RTT
;
475 #endif /* USE_ICMP */
477 /* The peer is allowed to use this cache */
478 ICP2State
*state
= new ICP2State (header
, icp_request
);
484 state
->url
= xstrdup (url
);
486 state
->flags
= flags
;
490 state
->src_rtt
= src_rtt
;
492 StoreEntry::getPublic (state
, url
, METHOD_GET
);
494 HTTPMSGUNLOCK(icp_request
);
498 _icp_common_t::handleReply(char *buf
, IpAddress
&from
)
500 if (neighbors_do_private_keys
&& reqnum
== 0) {
501 debugs(12, 0, "icpHandleIcpV2: Neighbor " << from
<< " returned reqnum = 0");
502 debugs(12, 0, "icpHandleIcpV2: Disabling use of private keys");
503 neighbors_do_private_keys
= 0;
506 char *url
= buf
+ sizeof(icp_common_t
);
507 debugs(12, 3, "icpHandleIcpV2: " << icp_opcode_str
[opcode
] << " from " << from
<< " for '" << url
<< "'");
509 const cache_key
*key
= icpGetCacheKey(url
, (int) reqnum
);
510 /* call neighborsUdpAck even if ping_status != PING_WAITING */
511 neighborsUdpAck(key
, this, from
);
515 icpHandleIcpV2(int fd
, IpAddress
&from
, char *buf
, int len
)
518 debugs(12, 3, "icpHandleIcpV2: ICP message is too small");
522 icp_common_t
header(buf
, len
);
524 * Length field should match the number of bytes read
527 if (len
!= header
.length
) {
528 debugs(12, 3, "icpHandleIcpV2: ICP message is too small");
532 switch (header
.opcode
) {
535 /* We have a valid packet */
536 doV2Query(fd
, from
, buf
, header
);
547 case ICP_MISS_NOFETCH
:
548 header
.handleReply(buf
, from
);
557 debugs(12, 0, "icpHandleIcpV2: UNKNOWN OPCODE: " << header
.opcode
<< " from " << from
);
565 icpPktDump(icp_common_t
* pkt
)
570 debugs(12, 9, "opcode: " << std::setw(3) << pkt
->opcode
<< " " << icp_opcode_str
[pkt
->opcode
]);
571 debugs(12, 9, "version: "<< std::left
<< std::setw(8) << pkt
->version
);
572 debugs(12, 9, "length: "<< std::left
<< std::setw(8) << ntohs(pkt
->length
));
573 debugs(12, 9, "reqnum: "<< std::left
<< std::setw(8) << ntohl(pkt
->reqnum
));
574 debugs(12, 9, "flags: "<< std::left
<< std::hex
<< std::setw(8) << ntohl(pkt
->flags
));
575 a
= (struct in_addr
)pkt
->shostid
;
576 debugs(12, 9, "shostid: " << a
);
577 debugs(12, 9, "payload: " << (char *) pkt
+ sizeof(icp_common_t
));
583 icpHandleUdp(int sock
, void *data
)
585 int *N
= &incoming_sockets_accepted
;
588 LOCAL_ARRAY(char, buf
, SQUID_UDP_SO_RCVBUF
);
591 int max
= INCOMING_ICP_MAX
;
592 commSetSelect(sock
, COMM_SELECT_READ
, icpHandleUdp
, NULL
, 0);
595 len
= comm_udp_recvfrom(sock
,
597 SQUID_UDP_SO_RCVBUF
- 1,
605 if (ignoreErrno(errno
))
609 /* Some Linux systems seem to set the FD for reading and then
610 * return ECONNREFUSED when sendto() fails and generates an ICMP
611 * port unreachable message. */
612 /* or maybe an EHOSTUNREACH "No route to host" message */
613 if (errno
!= ECONNREFUSED
&& errno
!= EHOSTUNREACH
)
616 debugs(50, 1, "icpHandleUdp: FD " << sock
<< " recvfrom: " << xstrerror());
622 icpCount(buf
, RECV
, (size_t) len
, 0);
624 debugs(12, 4, "icpHandleUdp: FD " << sock
<< ": received " <<
625 (unsigned long int)len
<< " bytes from " << from
);
627 #ifdef ICP_PACKET_DUMP
632 if ((size_t) len
< sizeof(icp_common_t
)) {
633 debugs(12, 4, "icpHandleUdp: Ignoring too-small UDP packet");
637 icp_version
= (int) buf
[1]; /* cheat! */
639 if (icp_version
== ICP_VERSION_2
)
640 icpHandleIcpV2(sock
, from
, buf
, len
);
641 else if (icp_version
== ICP_VERSION_3
)
642 icpHandleIcpV3(sock
, from
, buf
, len
);
644 debugs(12, 1, "WARNING: Unused ICP version " << icp_version
<<
645 " received from " << from
);
650 icpConnectionsOpen(void)
656 struct addrinfo
*xai
= NULL
;
660 if ((port
= Config
.Port
.icp
) <= 0)
665 addr
= Config
.Addrs
.udp_incoming
;
667 theInIcpConnection
= comm_open_listener(SOCK_DGRAM
,
674 if (theInIcpConnection
< 0)
675 fatal("Cannot open ICP Port");
677 commSetSelect(theInIcpConnection
,
683 for (s
= Config
.mcast_group_list
; s
; s
= s
->next
)
684 ipcache_nbgethostbyname(s
->key
, mcastJoinGroups
, NULL
);
686 debugs(12, 1, "Accepting ICP messages at " << addr
<< ", FD " << theInIcpConnection
<< ".");
688 addr
.SetEmpty(); // clear for next use.
689 addr
= Config
.Addrs
.udp_outgoing
;
690 if ( !addr
.IsNoAddr() ) {
693 theOutIcpConnection
= comm_open_listener(SOCK_DGRAM
,
700 if (theOutIcpConnection
< 0)
701 fatal("Cannot open Outgoing ICP Port");
703 commSetSelect(theOutIcpConnection
,
709 debugs(12, 1, "Outgoing ICP messages on port " << addr
.GetPort() << ", FD " << theOutIcpConnection
<< ".");
711 fd_note(theOutIcpConnection
, "Outgoing ICP socket");
713 fd_note(theInIcpConnection
, "Incoming ICP socket");
715 theOutIcpConnection
= theInIcpConnection
;
718 theOutICPAddr
.SetEmpty();
720 theOutICPAddr
.InitAddrInfo(xai
);
722 x
= getsockname(theOutIcpConnection
, xai
->ai_addr
, &xai
->ai_addrlen
);
725 debugs(50, 1, "theOutIcpConnection FD " << theOutIcpConnection
<< ": getsockname: " << xstrerror());
727 theOutICPAddr
= *xai
;
729 theOutICPAddr
.FreeAddrInfo(xai
);
733 * icpConnectionShutdown only closes the 'in' socket if it is
734 * different than the 'out' socket.
737 icpConnectionShutdown(void)
739 if (theInIcpConnection
< 0)
742 if (theInIcpConnection
!= theOutIcpConnection
) {
743 debugs(12, 1, "FD " << theInIcpConnection
<< " Closing ICP connection");
744 comm_close(theInIcpConnection
);
748 * Here we set 'theInIcpConnection' to -1 even though the ICP 'in'
749 * and 'out' sockets might be just one FD. This prevents this
750 * function from executing repeatedly. When we are really ready to
751 * exit or restart, main will comm_close the 'out' descriptor.
753 theInIcpConnection
= -1;
756 * Normally we only write to the outgoing ICP socket, but
757 * we also have a read handler there to catch messages sent
758 * to that specific interface. During shutdown, we must
759 * disable reading on the outgoing socket.
761 assert(theOutIcpConnection
> -1);
763 commSetSelect(theOutIcpConnection
, COMM_SELECT_READ
, NULL
, NULL
, 0);
767 icpConnectionClose(void)
769 icpConnectionShutdown();
771 if (theOutIcpConnection
> -1) {
772 debugs(12, 1, "FD " << theOutIcpConnection
<< " Closing ICP connection");
773 comm_close(theOutIcpConnection
);
774 theOutIcpConnection
= -1;
779 icpCount(void *buf
, int which
, size_t len
, int delay
)
781 icp_common_t
*icp
= (icp_common_t
*) buf
;
783 if (len
< sizeof(*icp
))
787 statCounter
.icp
.pkts_sent
++;
788 kb_incr(&statCounter
.icp
.kbytes_sent
, len
);
790 if (ICP_QUERY
== icp
->opcode
) {
791 statCounter
.icp
.queries_sent
++;
792 kb_incr(&statCounter
.icp
.q_kbytes_sent
, len
);
794 statCounter
.icp
.replies_sent
++;
795 kb_incr(&statCounter
.icp
.r_kbytes_sent
, len
);
796 /* this is the sent-reply service time */
797 statHistCount(&statCounter
.icp
.reply_svc_time
, delay
);
800 if (ICP_HIT
== icp
->opcode
)
801 statCounter
.icp
.hits_sent
++;
802 } else if (RECV
== which
) {
803 statCounter
.icp
.pkts_recv
++;
804 kb_incr(&statCounter
.icp
.kbytes_recv
, len
);
806 if (ICP_QUERY
== icp
->opcode
) {
807 statCounter
.icp
.queries_recv
++;
808 kb_incr(&statCounter
.icp
.q_kbytes_recv
, len
);
810 statCounter
.icp
.replies_recv
++;
811 kb_incr(&statCounter
.icp
.r_kbytes_recv
, len
);
812 /* statCounter.icp.query_svc_time set in clientUpdateCounters */
815 if (ICP_HIT
== icp
->opcode
)
816 statCounter
.icp
.hits_recv
++;
820 #define N_QUERIED_KEYS 8192
821 #define N_QUERIED_KEYS_MASK 8191
822 static cache_key queried_keys
[N_QUERIED_KEYS
][SQUID_MD5_DIGEST_LENGTH
];
825 icpSetCacheKey(const cache_key
* key
)
827 static int reqnum
= 0;
832 storeKeyCopy(queried_keys
[reqnum
& N_QUERIED_KEYS_MASK
], key
);
838 icpGetCacheKey(const char *url
, int reqnum
)
840 if (neighbors_do_private_keys
&& reqnum
)
841 return queried_keys
[reqnum
& N_QUERIED_KEYS_MASK
];
843 return storeKeyPublic(url
, METHOD_GET
);