#include #include #include #include #include #include #include #include #include #include "linux_ppp.h" #include "log.h" #include "events.h" #include "ppp.h" #include "ppp_ccp.h" #include "ppp_ipv6cp.h" #include "ipdb.h" #include "memdebug.h" #define INTF_ID_FIXED 0 #define INTF_ID_RANDOM 1 #define INTF_ID_CSID 2 #define INTF_ID_IPV4 3 static int conf_check_exists; static int conf_intf_id = INTF_ID_FIXED; static uint64_t conf_intf_id_val = 1; static int conf_peer_intf_id = INTF_ID_FIXED; static uint64_t conf_peer_intf_id_val = 2; static int conf_accept_peer_intf_id; static struct ipv6cp_option_t *ipaddr_init(struct ppp_ipv6cp_t *ipv6cp); static void ipaddr_free(struct ppp_ipv6cp_t *ipv6cp, struct ipv6cp_option_t *opt); static int ipaddr_send_conf_req(struct ppp_ipv6cp_t *ipv6cp, struct ipv6cp_option_t *opt, uint8_t *ptr); static int ipaddr_send_conf_nak(struct ppp_ipv6cp_t *ipv6cp, struct ipv6cp_option_t *opt, uint8_t *ptr); static int ipaddr_recv_conf_req(struct ppp_ipv6cp_t *ipv6cp, struct ipv6cp_option_t *opt, uint8_t *ptr); //static int ipaddr_recv_conf_ack(struct ppp_ipv6cp_t *ipv6cp, struct ipv6cp_option_t *opt, uint8_t *ptr); static void ipaddr_print(void (*print)(const char *fmt,...),struct ipv6cp_option_t*, uint8_t *ptr); static void put_ipv6_item(struct ap_session *ses, struct ipv6db_item_t *ip6); struct ipaddr_option_t { struct ipv6cp_option_t opt; int started:1; struct ppp_t *ppp; }; static struct ipv6cp_option_handler_t ipaddr_opt_hnd = { .init = ipaddr_init, .send_conf_req = ipaddr_send_conf_req, .send_conf_nak = ipaddr_send_conf_nak, .recv_conf_req = ipaddr_recv_conf_req, .free = ipaddr_free, .print = ipaddr_print, }; /* ipdb backend used for generating a link-local address when all other * backends (like radius and ipv6pool) failed to assign IPv6 addresses. * This backend isn't registered to ipdb as it's only used as a fallback * during IPv6CP negociation. */ static struct ipdb_t ipv6db = { .put_ipv6 = put_ipv6_item }; static void put_ipv6_item(struct ap_session *ses, struct ipv6db_item_t *ip6) { _free(ip6); } static int gen_ipv6_item(struct ap_session *ses) { struct ipv6db_item_t *ip6 = NULL; ip6 = _malloc(sizeof(*ip6)); if (ip6 == NULL) { log_ppp_warn("ppp: allocation of IPv6 address failed\n"); return -1; } memset(ip6, 0, sizeof(*ip6)); ip6->owner = &ipv6db; ip6->intf_id = 0; ip6->peer_intf_id = 0; INIT_LIST_HEAD(&ip6->addr_list); ses->ipv6 = ip6; return 0; } static struct ipv6cp_option_t *ipaddr_init(struct ppp_ipv6cp_t *ipv6cp) { struct ipaddr_option_t *ipaddr_opt = _malloc(sizeof(*ipaddr_opt)); memset(ipaddr_opt, 0, sizeof(*ipaddr_opt)); ipaddr_opt->opt.id = CI_INTFID; ipaddr_opt->opt.len = 10; ipaddr_opt->ppp = ipv6cp->ppp; return &ipaddr_opt->opt; } static void ipaddr_free(struct ppp_ipv6cp_t *ipv6cp, struct ipv6cp_option_t *opt) { struct ipaddr_option_t *ipaddr_opt=container_of(opt,typeof(*ipaddr_opt),opt); _free(ipaddr_opt); } static int check_exists(struct ppp_t *self_ppp) { struct ap_session *ses; struct ipv6db_addr_t *a1, *a2; int r = 0; pthread_rwlock_rdlock(&ses_lock); list_for_each_entry(ses, &ses_list, entry) { if (ses->terminating) continue; if (!ses->ipv6) continue; if (ses == &self_ppp->ses) continue; list_for_each_entry(a1, &ses->ipv6->addr_list, entry) { list_for_each_entry(a2, &self_ppp->ses.ipv6->addr_list, entry) { if (a1->addr.s6_addr32[0] == a2->addr.s6_addr32[0] && a1->addr.s6_addr32[1] == a2->addr.s6_addr32[1]) { log_ppp_warn("ppp: requested IPv6 address already assigned to %s\n", ses->ifname); r = 1; goto out; } } } } out: pthread_rwlock_unlock(&ses_lock); return r; } static uint64_t generate_intf_id(struct ppp_t *ppp) { uint64_t id = 0; switch (conf_intf_id) { case INTF_ID_FIXED: return conf_intf_id_val; break; //case INTF_ID_RANDOM: default: read(urandom_fd, &id, 8); break; } return id; } static uint64_t generate_peer_intf_id(struct ppp_t *ppp) { char str[4]; int i, n; union { uint64_t intf_id; uint16_t addr16[4]; } u; switch (conf_peer_intf_id) { case INTF_ID_FIXED: return conf_peer_intf_id_val; break; case INTF_ID_RANDOM: read(urandom_fd, &u, sizeof(u)); break; case INTF_ID_CSID: break; case INTF_ID_IPV4: if (ppp->ses.ipv4) { for (i = 0; i < 4; i++) { sprintf(str, "%i", (ppp->ses.ipv4->peer_addr >> (i*8)) & 0xff); sscanf(str, "%x", &n); u.addr16[i] = htons(n); } } else return 0; } return u.intf_id; } static int alloc_ip(struct ppp_t *ppp) { ppp->ses.ipv6 = ipdb_get_ipv6(&ppp->ses); if (!ppp->ses.ipv6) { if (gen_ipv6_item(&ppp->ses) < 0) { log_ppp_warn("ppp: no free IPv6 address\n"); return IPV6CP_OPT_CLOSE; } } if (!ppp->ses.ipv6->intf_id) ppp->ses.ipv6->intf_id = generate_intf_id(ppp); if (conf_check_exists && check_exists(ppp)) return IPV6CP_OPT_FAIL; return 0; } static int ipaddr_send_conf_req(struct ppp_ipv6cp_t *ipv6cp, struct ipv6cp_option_t *opt, uint8_t *ptr) { struct ipaddr_option_t *ipaddr_opt = container_of(opt, typeof(*ipaddr_opt), opt); struct ipv6cp_opt64_t *opt64 = (struct ipv6cp_opt64_t *)ptr; int r; if (!ipv6cp->ppp->ses.ipv6) { r = alloc_ip(ipv6cp->ppp); if (r) return r; } opt64->hdr.id = CI_INTFID; opt64->hdr.len = 10; opt64->val = ipv6cp->ppp->ses.ipv6->intf_id; return 10; } static int ipaddr_send_conf_nak(struct ppp_ipv6cp_t *ipv6cp, struct ipv6cp_option_t *opt, uint8_t *ptr) { struct ipaddr_option_t *ipaddr_opt = container_of(opt, typeof(*ipaddr_opt), opt); struct ipv6cp_opt64_t *opt64 = (struct ipv6cp_opt64_t *)ptr; opt64->hdr.id = CI_INTFID; opt64->hdr.len = 10; opt64->val = ipv6cp->ppp->ses.ipv6->peer_intf_id; return 10; } static int ipaddr_recv_conf_req(struct ppp_ipv6cp_t *ipv6cp, struct ipv6cp_option_t *opt, uint8_t *ptr) { struct ipaddr_option_t *ipaddr_opt = container_of(opt, typeof(*ipaddr_opt), opt); struct ipv6cp_opt64_t *opt64 = (struct ipv6cp_opt64_t* )ptr; int r; if (opt64->hdr.len != 10) return IPV6CP_OPT_REJ; if (!ipv6cp->ppp->ses.ipv6) { r = alloc_ip(ipv6cp->ppp); if (r) return r; } if (conf_accept_peer_intf_id && opt64->val) ipv6cp->ppp->ses.ipv6->peer_intf_id = opt64->val; else if (!ipv6cp->ppp->ses.ipv6->peer_intf_id) { ipv6cp->ppp->ses.ipv6->peer_intf_id = generate_peer_intf_id(ipv6cp->ppp); if (!ipv6cp->ppp->ses.ipv6->peer_intf_id) return IPV6CP_OPT_TERMACK; } if (opt64->val && ipv6cp->ppp->ses.ipv6->peer_intf_id == opt64->val && opt64->val != ipv6cp->ppp->ses.ipv6->intf_id) { ipv6cp->delay_ack = ccp_ipcp_started(ipv6cp->ppp); ipaddr_opt->started = 1; return IPV6CP_OPT_ACK; } return IPV6CP_OPT_NAK; } static void ipaddr_print(void (*print)(const char *fmt,...), struct ipv6cp_option_t *opt, uint8_t *ptr) { struct ipaddr_option_t *ipaddr_opt = container_of(opt, typeof(*ipaddr_opt), opt); struct ipv6cp_opt64_t *opt64 = (struct ipv6cp_opt64_t *)ptr; struct in6_addr a; if (ptr) *(uint64_t *)(a.s6_addr + 8) = opt64->val; else *(uint64_t *)(a.s6_addr + 8) = ipaddr_opt->ppp->ses.ipv6->intf_id; print("", ntohs(a.s6_addr16[4]), ntohs(a.s6_addr16[5]), ntohs(a.s6_addr16[6]), ntohs(a.s6_addr16[7])); } static uint64_t parse_intfid(const char *opt) { union { uint64_t u64; uint16_t u16[4]; } u; int n[4]; int i; if (sscanf(opt, "%x:%x:%x:%x", &n[0], &n[1], &n[2], &n[3]) != 4) goto err; for (i = 0; i < 4; i++) { if (n[i] < 0 || n[i] > 0xffff) goto err; u.u16[i] = htons(n[i]); } return u.u64; err: log_error("ppp:ipv6cp: failed to parse intf-id '%s'\n", opt); conf_intf_id = INTF_ID_RANDOM; return 0; } static void load_config(void) { const char *opt; opt = conf_get_opt("ppp", "check-ip"); if (!opt) opt = conf_get_opt("common", "check-ip"); if (opt && atoi(opt) >= 0) conf_check_exists = atoi(opt) > 0; opt = conf_get_opt("ppp", "ipv6-intf-id"); if (opt) { if (!strcmp(opt, "random")) conf_intf_id = INTF_ID_RANDOM; else { conf_intf_id = INTF_ID_FIXED; conf_intf_id_val = parse_intfid(opt); } } opt = conf_get_opt("ppp", "ipv6-peer-intf-id"); if (opt) { if (!strcmp(opt, "random")) conf_peer_intf_id = INTF_ID_RANDOM; else if (!strcmp(opt, "calling-sid")) conf_peer_intf_id = INTF_ID_CSID; else if (!strcmp(opt, "ipv4")) conf_peer_intf_id = INTF_ID_IPV4; else { conf_peer_intf_id = INTF_ID_FIXED; conf_peer_intf_id_val = parse_intfid(opt); } } opt = conf_get_opt("ppp", "ipv6-accept-peer-intf-id"); if (opt) conf_accept_peer_intf_id = atoi(opt); } static void init() { if (sock6_fd < 0) return; ipv6cp_option_register(&ipaddr_opt_hnd); load_config(); triton_event_register_handler(EV_CONFIG_RELOAD, (triton_event_func)load_config); } DEFINE_INIT(5, init);