/** * Process upstream p2p bandwidth limits. * * @param[in] sess Session. * @param[in] packet_len Packet length. * @param[in] iph IP header. * @param[in] flow_dir Flow direction. * @return Zero on pass. */ static int packet_process_p2p_ipv4(struct zsession *sess, size_t packet_len, struct ip *iph, struct l4_data *l4, enum flow_dir flow_dir) { if (PROTO_MAX == l4->proto) { return 0; } uint16_t port = ntohs((DIR_UP == flow_dir) ? *l4->dst_port : *l4->src_port); pthread_rwlock_rdlock(&sess->lock_client); // p2p police enabled and port greater than 1024 and not whitelisted if (sess->client->p2p_policy && (port >= 1024) && !utarray_find(&zcfg()->p2p_ports_whitelist, &port, uint16_cmp)) { uint64_t speed = spdm_calc(&sess->client->speed[flow_dir]); // 1/4 of bw limit uint64_t throttle_speed = token_bucket_get_max(&sess->client->band[flow_dir]) / 4; uint64_t diff = zclock(false) - sess->client->last_p2p_throttle; if ((speed > throttle_speed) || (diff < P2P_THROTTLE_TIME)) { unsigned upstream_id = IPTOS_DSCP(iph->ip_tos) >> 2; struct token_bucket *bucket = &zinst()->upstreams[upstream_id].band[flow_dir]; if (0 != token_bucket_update(bucket, packet_len)) { return -1; } struct speed_meter *spd = &zinst()->upstreams[upstream_id].speed[flow_dir]; spdm_update(spd, packet_len); diff = zclock(false) - atomic_load_explicit(&sess->client->last_p2p_throttle, memory_order_acquire); if (diff > P2P_THROTTLE_TIME) { atomic_store_explicit(&sess->client->last_p2p_throttle, zclock(false), memory_order_release); } }
/** * Remove session from storage. * @param[in] sess */ void session_remove(struct zsession *sess) { size_t sidx = STORAGE_IDX(sess->ip); pthread_rwlock_wrlock(&zinst()->sessions_lock[sidx]); HASH_DELETE(hh, zinst()->sessions[sidx], sess); pthread_rwlock_unlock(&zinst()->sessions_lock[sidx]); session_release(sess); // release from session hash }
/** * Destroy session. * @param[in] sess */ void session_destroy(struct zsession *sess) { // update counters __atomic_sub_fetch(&zinst()->sessions_cnt, 1, __ATOMIC_RELAXED); if (0 == sess->client->id) { __atomic_sub_fetch(&zinst()->unauth_sessions_cnt, 1, __ATOMIC_RELAXED); } pthread_rwlock_destroy(&sess->lock_client); client_session_remove(sess->client, sess); client_release(sess->client); if(sess->nat) znat_destroy(sess->nat); free(sess); }
/** * Acquire existing session from sotrage or create new one. * All session MUST be requested through this function. * @param[in] ip IPv4 address of client. * @param[in] existing_only Do only existing sssion search. * @return New client. */ struct zsession *session_acquire(uint32_t ip, bool existing_only) { struct zsession *sess = NULL; size_t sidx = STORAGE_IDX(ip); // search for existing session pthread_rwlock_rdlock(&zinst()->sessions_lock[sidx]); HASH_FIND(hh, zinst()->sessions[sidx], &ip, sizeof(ip), sess); if (NULL != sess) { __atomic_add_fetch(&sess->refcnt, 1, __ATOMIC_RELAXED); } pthread_rwlock_unlock(&zinst()->sessions_lock[sidx]); // or create new session if (!existing_only && NULL == sess) { pthread_rwlock_wrlock(&zinst()->sessions_lock[sidx]); HASH_FIND(hh, zinst()->sessions[sidx], &ip, sizeof(ip), sess); if (NULL != sess) { __atomic_add_fetch(&sess->refcnt, 1, __ATOMIC_RELAXED); } else { sess = session_create(); sess->ip = ip; __atomic_store_n(&sess->last_activity, ztime(false), __ATOMIC_RELAXED); __atomic_add_fetch(&sess->refcnt, 1, __ATOMIC_RELAXED); // sessions storage reference HASH_ADD(hh, zinst()->sessions[sidx], ip, sizeof(ip), sess); } pthread_rwlock_unlock(&zinst()->sessions_lock[sidx]); } return sess; }
/** * Idle timeout for session reached in: * - ARP inspection is ON and DHCP lease is expired. * - ARP inspection is OFF and last activity was not earlier than default dhcp lease time. * @param[in] sess Session. * @param[in] now Current time. * @return bool */ static inline bool overlord_sess_is_idle_timeout(struct zsession *sess, uint64_t now) { if (atomic_load_explicit(&zinst()->arp.mode, memory_order_acquire)) { return now > atomic_load_explicit(&sess->dhcp_lease_end, memory_order_acquire); } else { return (now - zcfg()->dhcp_default_lease_time) > atomic_load_explicit(&sess->last_activity, memory_order_acquire); } }
/** * Create new empty session. * @return New session pointer. */ struct zsession *session_create() { struct zsession *sess = malloc(sizeof(*sess)); bzero(sess, sizeof(*sess)); sess->client = client_create(); client_session_add(sess->client, sess); // set default values sess->refcnt = 1; // caller references this entry pthread_spin_init(&sess->_nat_lock, PTHREAD_PROCESS_PRIVATE); pthread_rwlock_init(&sess->lock_client, NULL); __atomic_add_fetch(&zinst()->sessions_cnt, 1, __ATOMIC_RELAXED); __atomic_add_fetch(&zinst()->unauth_sessions_cnt, 1, __ATOMIC_RELAXED); return sess; }
/** * Traverse session storage from idx_begin to idx_end. */ void overlord_run(size_t idx_begin, size_t idx_end) { for (size_t i = idx_begin; i < idx_end; i++) { struct zsession *sess, *tmp_sess; pthread_rwlock_rdlock(&zinst()->sessions_lock[i]); HASH_ITER(hh, zinst()->sessions[i], sess, tmp_sess) { pthread_rwlock_unlock(&zinst()->sessions_lock[i]); overlord_dns_attack_detect(sess); overlord_serve_session(sess); if (unlikely(zero_is_abort())) { return; } pthread_rwlock_rdlock(&zinst()->sessions_lock[i]); } pthread_rwlock_unlock(&zinst()->sessions_lock[i]); }
/** * Parse authentication reply radius attributes. * @param[in] session Session. * @param[in] attrs Radius attributes. * @param[in] rules Client rules. */ static void zrad_auth_parse(zsession_t *session, const VALUE_PAIR *attrs, zclient_rules_t *rules) { zclient_rules_init(rules); for (; likely(NULL != attrs); attrs = attrs->next) { switch (attrs->attribute) { case PW_FILTER_ID: zclient_rule_parse(zinst()->client_rule_parser, rules, attrs->strvalue); break; case PW_SESSION_TIMEOUT: atomic_store_release(&session->timeout, SEC2USEC(attrs->lvalue)); break; case PW_ACCT_INTERIM_INTERVAL: atomic_store_release(&session->acct_interval, SEC2USEC(attrs->lvalue)); break; case PW_IDLE_TIMEOUT: atomic_store_release(&session->idle_timeout, SEC2USEC(attrs->lvalue)); break; default: ZLOG(LOG_DEBUG, "Unknown radius attribute %d", attrs->attribute); } } }
/** * Authenticate and set client info. * @param[in] sess Client session. * @return Zero on success (or one of *_RC). */ static int session_authenticate(struct zsession *sess) { int ret = OTHER_RC; VALUE_PAIR *request_attrs = NULL, *response_attrs = NULL, *attrs = NULL; char msg[8192]; // WARNING: libfreeradius-client has unsafe working with this buffer. rc_handle *rh = zinst()->radh; struct in_addr ip_addr; char ip_str[INET_ADDRSTRLEN]; struct zcrules rules; crules_init(&rules); ip_addr.s_addr = htonl(sess->ip); if (unlikely(NULL == inet_ntop(AF_INET, &ip_addr, ip_str, sizeof(ip_str)))) { goto end; } if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_USER_NAME, ip_str, -1, 0))) { goto end; } if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_USER_PASSWORD, "", -1, 0))) { goto end; } if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_NAS_IDENTIFIER, zcfg()->radius_nas_identifier, -1, 0))) { goto end; } if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_CALLING_STATION_ID, ip_str, -1, 0))) { goto end; } ret = rc_auth(rh, 0, request_attrs, &response_attrs, msg); if (OK_RC != ret) { ZERO_LOG(LOG_ERR, "Session authentication failed for %s (code:%d)", ip_str, ret); goto end; } attrs = response_attrs; while (likely(NULL != attrs)) { switch (attrs->attribute) { case PW_FILTER_ID: crules_parse(&rules, attrs->strvalue); break; case PW_SESSION_TIMEOUT: atomic_store_explicit(&sess->max_duration, SEC2USEC(attrs->lvalue), memory_order_release); break; case PW_ACCT_INTERIM_INTERVAL: atomic_store_explicit(&sess->acct_interval, SEC2USEC(attrs->lvalue), memory_order_release); break; } attrs = attrs->next; } if (likely(rules.have.user_id && rules.have.login)) { struct zclient *client = sess->client; client_db_find_or_set_id(zinst()->client_db, rules.user_id, &client); if (client != sess->client) { // found pthread_rwlock_wrlock(&sess->lock_client); atomic_fetch_add_explicit(&client->refcnt, 1, memory_order_relaxed); client_release(sess->client); sess->client = client; client_session_add(sess->client, sess); pthread_rwlock_unlock(&sess->lock_client); } else { client_apply_rules(sess->client, &rules); } atomic_fetch_sub_explicit(&zinst()->unauth_sessions_cnt, 1, memory_order_release); // log successful authentication { UT_string rules_str; utstring_init(&rules_str); utstring_reserve(&rules_str, 1024); attrs = response_attrs; while (likely(NULL != attrs)) { switch (attrs->attribute) { case PW_FILTER_ID: utstring_printf(&rules_str, " %s", attrs->strvalue); break; default: break; } attrs = attrs->next; } zero_syslog(LOG_INFO, "Authenticated session %s (rules:%s)", ip_str, utstring_body(&rules_str)); utstring_done(&rules_str); } } else { ret = OTHER_RC; ZERO_LOG(LOG_ERR, "Session authentication failed for %s (code:%d)", ip_str, ret); } end: crules_free(&rules); if (request_attrs) rc_avpair_free(request_attrs); if (response_attrs) rc_avpair_free(response_attrs); return ret; }
/** * Send radius accounting packet. * @param[in] sess Session * @param[in] status Accounting status (PW_STATUS_START, PW_STATUS_STOP, PW_STATUS_ALIVE) * @param[in] cause Accounting termination cause (used only in case of PW_STATUS_STOP) * @return Zero on success (one of *_RC codes). */ static int session_accounting(struct zsession *sess, uint32_t status, uint32_t term_cause) { int ret = OTHER_RC; VALUE_PAIR *request_attrs = NULL; rc_handle *rh = zinst()->radh; struct in_addr ip_addr; char ip_str[INET_ADDRSTRLEN]; uint64_t traff_down = atomic_load_explicit(&sess->traff_down, memory_order_acquire); uint64_t traff_up = atomic_load_explicit(&sess->traff_up, memory_order_acquire); uint32_t octets_down = (uint32_t) (traff_down % UINT32_MAX); uint32_t octets_up = (uint32_t) (traff_up % UINT32_MAX); uint32_t packets_down = atomic_load_explicit(&sess->packets_down, memory_order_acquire) % UINT32_MAX; uint32_t packets_up = atomic_load_explicit(&sess->packets_up, memory_order_acquire) % UINT32_MAX; uint32_t gigawords_down = 0; uint32_t gigawords_up = 0; char session_id[255]; snprintf(session_id, sizeof(session_id), "%s-%" PRIu32, sess->client->login, sess->ip); ip_addr.s_addr = htonl(sess->ip); if (unlikely(NULL == inet_ntop(AF_INET, &ip_addr, ip_str, sizeof(ip_str)))) { goto end; } if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_CALLING_STATION_ID, ip_str, -1, 0))) { goto end; } if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_FRAMED_IP_ADDRESS, &sess->ip, -1, 0))) { goto end; } if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_USER_NAME, sess->client->login, -1, 0))) { goto end; } if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_ACCT_SESSION_ID, session_id, -1, 0))) { goto end; } if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_NAS_IDENTIFIER, zcfg()->radius_nas_identifier, -1, 0))) { goto end; } if (PW_STATUS_STOP == status) { if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_ACCT_TERMINATE_CAUSE, &term_cause, -1, 0))) { goto end; } } if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_ACCT_STATUS_TYPE, &status, -1, 0))) { goto end; } if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_ACCT_INPUT_OCTETS, &octets_down, -1, 0))) { goto end; } if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_ACCT_INPUT_PACKETS, &packets_down, -1, 0))) { goto end; } if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_ACCT_OUTPUT_OCTETS, &octets_up, -1, 0))) { goto end; } if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_ACCT_OUTPUT_PACKETS, &packets_down, -1, 0))) { goto end; } if (unlikely(UINT32_MAX < traff_down)) { gigawords_down = (uint32_t) (traff_down / UINT32_MAX); if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_ACCT_INPUT_GIGAWORDS, &gigawords_down, -1, 0))) { goto end; } } if (unlikely(UINT32_MAX < traff_up)) { gigawords_up = (uint32_t) (traff_up / UINT32_MAX); if (unlikely(NULL == rc_avpair_add(rh, &request_attrs, PW_ACCT_OUTPUT_GIGAWORDS, &gigawords_up, -1, 0))) { goto end; } } ret = rc_acct(rh, 0, request_attrs); if (unlikely(OK_RC != ret)) { ZERO_LOG(LOG_ERR, "radius accounting failed %s (code:%d)", ip_str, ret); goto end; } atomic_fetch_sub_explicit(&sess->traff_down, traff_down, memory_order_release); atomic_fetch_sub_explicit(&sess->traff_up, traff_up, memory_order_release); atomic_fetch_sub_explicit(&sess->packets_down, packets_down, memory_order_release); atomic_fetch_sub_explicit(&sess->packets_up, packets_up, memory_order_release); end: if (request_attrs) { rc_avpair_free(request_attrs); } return ret; }