/* * Copyright (C) 1996-2018 The Squid Software Foundation and contributors * * Squid software is distributed under GPLv2+ license and includes * contributions from numerous individuals and organizations. * Please see the COPYING and CONTRIBUTORS files for details. */ /* DEBUG: section 53 AS Number handling */ #include "squid.h" #include "acl/Acl.h" #include "acl/Asn.h" #include "acl/Checklist.h" #include "acl/DestinationAsn.h" #include "acl/DestinationIp.h" #include "acl/SourceAsn.h" #include "cache_cf.h" #include "FwdState.h" #include "HttpReply.h" #include "HttpRequest.h" #include "ipcache.h" #include "mgr/Registration.h" #include "radix.h" #include "RequestFlags.h" #include "SquidConfig.h" #include "Store.h" #include "StoreClient.h" #define WHOIS_PORT 43 #define AS_REQBUF_SZ 4096 /* BEGIN of definitions for radix tree entries */ /* 32/128 bits address in memory with length */ class m_ADDR { public: uint8_t len; Ip::Address addr; m_ADDR() : len(sizeof(Ip::Address)) {}; }; /* END of definitions for radix tree entries */ /* Head for ip to asn radix tree */ struct squid_radix_node_head *AS_tree_head; /* explicit instantiation required for some systems */ /// \cond AUTODOCS_IGNORE template cbdata_type CbDataList::CBDATA_CbDataList; /// \endcond /** * Structure for as number information. it could be simply * a list but it's coded as a structure for future * enhancements (e.g. expires) */ struct as_info { CbDataList *as_number; time_t expires; /* NOTUSED */ }; class ASState { public: ASState(); ~ASState(); StoreEntry *entry; store_client *sc; HttpRequest::Pointer request; int as_number; int64_t offset; int reqofs; char reqbuf[AS_REQBUF_SZ]; bool dataRead; private: CBDATA_CLASS2(ASState); }; CBDATA_CLASS_INIT(ASState); ASState::ASState() : entry(NULL), sc(NULL), request(NULL), as_number(0), offset(0), reqofs(0), dataRead(false) { memset(reqbuf, 0, AS_REQBUF_SZ); } ASState::~ASState() { debugs(53, 3, entry->url()); storeUnregister(sc, entry, this); entry->unlock("~ASState"); } /** entry into the radix tree */ struct rtentry_t { struct squid_radix_node e_nodes[2]; as_info *e_info; m_ADDR e_addr; m_ADDR e_mask; }; static int asnAddNet(char *, int); static void asnCacheStart(int as); static STCB asHandleReply; #if defined(__cplusplus) extern "C" { #endif static int destroyRadixNode(struct squid_radix_node *rn, void *w); static int printRadixNode(struct squid_radix_node *rn, void *sentry); #if defined(__cplusplus) } #endif void asnAclInitialize(ACL * acls); static void destroyRadixNodeInfo(as_info *); static OBJH asnStats; /* PUBLIC */ int asnMatchIp(CbDataList *data, Ip::Address &addr) { struct squid_radix_node *rn; as_info *e; m_ADDR m_addr; CbDataList *a = NULL; CbDataList *b = NULL; debugs(53, 3, "asnMatchIp: Called for " << addr ); if (AS_tree_head == NULL) return 0; if (addr.isNoAddr()) return 0; if (addr.isAnyAddr()) return 0; m_addr.addr = addr; rn = squid_rn_match(&m_addr, AS_tree_head); if (rn == NULL) { debugs(53, 3, "asnMatchIp: Address not in as db."); return 0; } debugs(53, 3, "asnMatchIp: Found in db!"); e = ((rtentry_t *) rn)->e_info; assert(e); for (a = data; a; a = a->next) for (b = e->as_number; b; b = b->next) if (a->element == b->element) { debugs(53, 5, "asnMatchIp: Found a match!"); return 1; } debugs(53, 5, "asnMatchIp: AS not in as db."); return 0; } void ACLASN::prepareForUse() { for (CbDataList *i = data; i; i = i-> next) asnCacheStart(i->element); } static void asnRegisterWithCacheManager(void) { Mgr::RegisterAction("asndb", "AS Number Database", asnStats, 0, 1); } /* initialize the radix tree structure */ SQUIDCEXTERN int squid_max_keylen; /* yuck.. this is in lib/radix.c */ void asnInit(void) { static bool inited = false; squid_max_keylen = 40; if (!inited) { inited = true; squid_rn_init(); } squid_rn_inithead(&AS_tree_head, 8); asnRegisterWithCacheManager(); } void asnFreeMemory(void) { squid_rn_walktree(AS_tree_head, destroyRadixNode, AS_tree_head); destroyRadixNode((struct squid_radix_node *) 0, (void *) AS_tree_head); } static void asnStats(StoreEntry * sentry) { storeAppendPrintf(sentry, "Address \tAS Numbers\n"); squid_rn_walktree(AS_tree_head, printRadixNode, sentry); } /* PRIVATE */ static void asnCacheStart(int as) { LOCAL_ARRAY(char, asres, 4096); StoreEntry *e; ASState *asState = new ASState; debugs(53, 3, "AS " << as); snprintf(asres, 4096, "whois://%s/!gAS%d", Config.as_whois_server, as); asState->as_number = as; asState->request = HttpRequest::CreateFromUrl(asres); assert(asState->request != NULL); if ((e = storeGetPublic(asres, Http::METHOD_GET)) == NULL) { e = storeCreateEntry(asres, asres, RequestFlags(), Http::METHOD_GET); asState->sc = storeClientListAdd(e, asState); FwdState::fwdStart(Comm::ConnectionPointer(), e, asState->request.getRaw()); } else { e->lock("Asn"); asState->sc = storeClientListAdd(e, asState); } asState->entry = e; StoreIOBuffer readBuffer (AS_REQBUF_SZ, asState->offset, asState->reqbuf); storeClientCopy(asState->sc, e, readBuffer, asHandleReply, asState); } static void asHandleReply(void *data, StoreIOBuffer result) { ASState *asState = (ASState *)data; StoreEntry *e = asState->entry; char *s; char *t; char *buf = asState->reqbuf; int leftoversz = -1; debugs(53, 3, "asHandleReply: Called with size=" << (unsigned int)result.length); debugs(53, 3, "asHandleReply: buffer='" << buf << "'"); /* First figure out whether we should abort the request */ if (EBIT_TEST(e->flags, ENTRY_ABORTED)) { delete asState; return; } if (result.length == 0 && asState->dataRead) { debugs(53, 3, "asHandleReply: Done: " << e->url()); delete asState; return; } else if (result.flags.error) { debugs(53, DBG_IMPORTANT, "asHandleReply: Called with Error set and size=" << (unsigned int) result.length); delete asState; return; } else if (e->getReply()->sline.status() != Http::scOkay) { debugs(53, DBG_IMPORTANT, "WARNING: AS " << asState->as_number << " whois request failed"); delete asState; return; } /* * Next, attempt to parse our request * Remembering that the actual buffer size is retsize + reqofs! */ s = buf; while ((size_t)(s - buf) < result.length + asState->reqofs && *s != '\0') { while (*s && xisspace(*s)) ++s; for (t = s; *t; ++t) { if (xisspace(*t)) break; } if (*t == '\0') { /* oof, word should continue on next block */ break; } *t = '\0'; debugs(53, 3, "asHandleReply: AS# " << s << " (" << asState->as_number << ")"); asnAddNet(s, asState->as_number); s = t + 1; asState->dataRead = true; } /* * Next, grab the end of the 'valid data' in the buffer, and figure * out how much data is left in our buffer, which we need to keep * around for the next request */ leftoversz = (asState->reqofs + result.length) - (s - buf); assert(leftoversz >= 0); /* * Next, copy the left over data, from s to s + leftoversz to the * beginning of the buffer */ memmove(buf, s, leftoversz); /* * Next, update our offset and reqofs, and kick off a copy if required */ asState->offset += result.length; asState->reqofs = leftoversz; debugs(53, 3, "asState->offset = " << asState->offset); if (e->store_status == STORE_PENDING) { debugs(53, 3, "asHandleReply: store_status == STORE_PENDING: " << e->url() ); StoreIOBuffer tempBuffer (AS_REQBUF_SZ - asState->reqofs, asState->offset, asState->reqbuf + asState->reqofs); storeClientCopy(asState->sc, e, tempBuffer, asHandleReply, asState); } else { StoreIOBuffer tempBuffer; debugs(53, 3, "asHandleReply: store complete, but data received " << e->url() ); tempBuffer.offset = asState->offset; tempBuffer.length = AS_REQBUF_SZ - asState->reqofs; tempBuffer.data = asState->reqbuf + asState->reqofs; storeClientCopy(asState->sc, e, tempBuffer, asHandleReply, asState); } } /** * add a network (addr, mask) to the radix tree, with matching AS number */ static int asnAddNet(char *as_string, int as_number) { struct squid_radix_node *rn; CbDataList **Tail = NULL; CbDataList *q = NULL; as_info *asinfo = NULL; Ip::Address mask; Ip::Address addr; char *t; int bitl; t = strchr(as_string, '/'); if (t == NULL) { debugs(53, 3, "asnAddNet: failed, invalid response from whois server."); return 0; } *t = '\0'; addr = as_string; bitl = atoi(t + 1); if (bitl < 0) bitl = 0; // INET6 TODO : find a better way of identifying the base IPA family for mask than this. t = strchr(as_string, '.'); // generate Netbits Format Mask mask.setNoAddr(); mask.applyMask(bitl, (t!=NULL?AF_INET:AF_INET6) ); debugs(53, 3, "asnAddNet: called for " << addr << "/" << mask ); rtentry_t *e = (rtentry_t *)xcalloc(1, sizeof(rtentry_t)); e->e_addr.addr = addr; e->e_mask.addr = mask; rn = squid_rn_lookup(&e->e_addr, &e->e_mask, AS_tree_head); if (rn != NULL) { asinfo = ((rtentry_t *) rn)->e_info; if (asinfo->as_number->find(as_number)) { debugs(53, 3, "asnAddNet: Ignoring repeated network '" << addr << "/" << bitl << "' for AS " << as_number); } else { debugs(53, 3, "asnAddNet: Warning: Found a network with multiple AS numbers!"); for (Tail = &asinfo->as_number; *Tail; Tail = &(*Tail)->next); q = new CbDataList (as_number); *(Tail) = q; e->e_info = asinfo; } } else { q = new CbDataList (as_number); asinfo = (as_info *)xmalloc(sizeof(as_info)); asinfo->as_number = q; squid_rn_addroute(&e->e_addr, &e->e_mask, AS_tree_head, e->e_nodes); rn = squid_rn_match(&e->e_addr, AS_tree_head); assert(rn != NULL); e->e_info = asinfo; } if (rn == 0) { /* assert might expand to nothing */ xfree(asinfo); delete q; xfree(e); debugs(53, 3, "asnAddNet: Could not add entry."); return 0; } e->e_info = asinfo; return 1; } static int destroyRadixNode(struct squid_radix_node *rn, void *w) { struct squid_radix_node_head *rnh = (struct squid_radix_node_head *) w; if (rn && !(rn->rn_flags & RNF_ROOT)) { rtentry_t *e = (rtentry_t *) rn; rn = squid_rn_delete(rn->rn_key, rn->rn_mask, rnh); if (rn == 0) debugs(53, 3, "destroyRadixNode: internal screwup"); destroyRadixNodeInfo(e->e_info); xfree(rn); } return 1; } static void destroyRadixNodeInfo(as_info * e_info) { CbDataList *prev = NULL; CbDataList *data = e_info->as_number; while (data) { prev = data; data = data->next; delete prev; } } static int printRadixNode(struct squid_radix_node *rn, void *_sentry) { StoreEntry *sentry = (StoreEntry *)_sentry; rtentry_t *e = (rtentry_t *) rn; CbDataList *q; as_info *asinfo; char buf[MAX_IPSTRLEN]; Ip::Address addr; Ip::Address mask; assert(e); assert(e->e_info); addr = e->e_addr.addr; mask = e->e_mask.addr; storeAppendPrintf(sentry, "%s/%d\t", addr.toStr(buf, MAX_IPSTRLEN), mask.cidr() ); asinfo = e->e_info; assert(asinfo->as_number); for (q = asinfo->as_number; q; q = q->next) storeAppendPrintf(sentry, " %d", q->element); storeAppendPrintf(sentry, "\n"); return 0; } ACLASN::~ACLASN() { if (data) delete data; } bool ACLASN::match(Ip::Address toMatch) { return asnMatchIp(data, toMatch); } SBufList ACLASN::dump() const { SBufList sl; CbDataList *ldata = data; while (ldata != NULL) { SBuf s; s.Printf("%d", ldata->element); sl.push_back(s); ldata = ldata->next; } return sl; } bool ACLASN::empty () const { return data == NULL; } void ACLASN::parse() { CbDataList **curlist = &data; CbDataList **Tail; CbDataList *q = NULL; char *t = NULL; for (Tail = curlist; *Tail; Tail = &((*Tail)->next)); while ((t = strtokFile())) { q = new CbDataList (atoi(t)); *(Tail) = q; Tail = &q->next; } } ACLData * ACLASN::clone() const { if (data) fatal ("cloning of ACLASN not implemented"); return new ACLASN(*this); } /* explicit template instantiation required for some systems */ template class ACLStrategised; ACL::Prototype ACLASN::SourceRegistryProtoype(&ACLASN::SourceRegistryEntry_, "src_as"); ACLStrategised ACLASN::SourceRegistryEntry_(new ACLASN, ACLSourceASNStrategy::Instance(), "src_as"); ACL::Prototype ACLASN::DestinationRegistryProtoype(&ACLASN::DestinationRegistryEntry_, "dst_as"); ACLStrategised ACLASN::DestinationRegistryEntry_(new ACLASN, ACLDestinationASNStrategy::Instance(), "dst_as"); int ACLSourceASNStrategy::match (ACLData * &data, ACLFilledChecklist *checklist, ACLFlags &) { return data->match(checklist->src_addr); } ACLSourceASNStrategy * ACLSourceASNStrategy::Instance() { return &Instance_; } ACLSourceASNStrategy ACLSourceASNStrategy::Instance_; int ACLDestinationASNStrategy::match (ACLData * &data, ACLFilledChecklist *checklist, ACLFlags &) { const ipcache_addrs *ia = ipcache_gethostbyname(checklist->request->GetHost(), IP_LOOKUP_IF_MISS); if (ia) { for (int k = 0; k < (int) ia->count; ++k) { if (data->match(ia->in_addrs[k])) return 1; } return 0; } else if (!checklist->request->flags.destinationIpLookedUp) { /* No entry in cache, lookup not attempted */ debugs(28, 3, "asnMatchAcl: Can't yet compare '" << AclMatchedName << "' ACL for '" << checklist->request->GetHost() << "'"); if (checklist->goAsync(DestinationIPLookup::Instance())) return -1; // else fall through to noaddr match, hiding the lookup failure (XXX) } Ip::Address noaddr; noaddr.setNoAddr(); return data->match(noaddr); } ACLDestinationASNStrategy * ACLDestinationASNStrategy::Instance() { return &Instance_; } ACLDestinationASNStrategy ACLDestinationASNStrategy::Instance_;