/* $Id: iptcrdr.c,v 1.1 2012/02/21 23:16:02 misho Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2008 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include #include #include #include #include #include #include #include #include #include #include #include #if IPTABLES_143 /* IPTABLES API version >= 1.4.3 */ #include #define ip_nat_multi_range nf_nat_multi_range #define ip_nat_range nf_nat_range #define IPTC_HANDLE struct iptc_handle * #else /* IPTABLES API version < 1.4.3 */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) #include #else #include #endif #define IPTC_HANDLE iptc_handle_t #endif #include "iptcrdr.h" #include "../upnpglobalvars.h" /* dummy init and shutdown functions */ int init_redirect(void) { return 0; } void shutdown_redirect(void) { return; } /* convert an ip address to string */ static int snprintip(char * dst, size_t size, uint32_t ip) { return snprintf(dst, size, "%u.%u.%u.%u", ip >> 24, (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff); } /* netfilter cannot store redirection descriptions, so we use our * own structure to store them */ struct rdr_desc { struct rdr_desc * next; unsigned short eport; short proto; char str[]; }; /* pointer to the chained list where descriptions are stored */ static struct rdr_desc * rdr_desc_list = 0; static void add_redirect_desc(unsigned short eport, int proto, const char * desc) { struct rdr_desc * p; size_t l; /* if(desc) {*/ if ((l = strlen(desc) + 1) == 1) l = 5; p = malloc(sizeof(struct rdr_desc) + l); if(p) { p->next = rdr_desc_list; p->eport = eport; p->proto = (short)proto; if(desc) memcpy(p->str, desc, l); else memcpy(p->str, "upnp", 4); rdr_desc_list = p; } /* }*/ } static void del_redirect_desc(unsigned short eport, int proto) { struct rdr_desc * p, * last; p = rdr_desc_list; last = 0; while(p) { if(p->eport == eport && p->proto == proto) { if(!last) rdr_desc_list = p->next; else last->next = p->next; free(p); return; } last = p; p = p->next; } } static void get_redirect_desc(unsigned short eport, int proto, char * desc, int desclen) { struct rdr_desc * p; if(!desc || (desclen == 0)) return; for(p = rdr_desc_list; p; p = p->next) { if(p->eport == eport && p->proto == (short)proto) { strncpy(desc, p->str, desclen); return; } } /* if no description was found, return miniupnpd as default */ strncpy(desc, "miniupnpd", desclen); } int get_redirect_desc_by_index(int index, unsigned short * eport, int * proto, char * desc, int desclen) { int i = 0; struct rdr_desc * p; if(!desc || (desclen == 0)) return -1; for(p = rdr_desc_list; p; p = p->next, i++) { if(i == index) { *eport = p->eport; *proto = (int)p->proto; strncpy(desc, p->str, desclen); return 0; } } return -1; } /* add_redirect_rule2() */ int add_redirect_rule2(const char * ifname, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc) { int r = addnatrule(proto, eport, iaddr, iport); if(r >= 0) add_redirect_desc(eport, proto, desc); return r; } int add_filter_rule2(const char * ifname, const char * iaddr, unsigned short eport, unsigned short iport, int proto, const char * desc) { return add_filter_rule(proto, iaddr, iport); } /* get_redirect_rule() * returns -1 if the rule is not found */ int get_redirect_rule(const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, u_int64_t * packets, u_int64_t * bytes) { int r = -1; IPTC_HANDLE h; const struct ipt_entry * e; const struct ipt_entry_target * target; const struct ip_nat_multi_range * mr; const struct ipt_entry_match *match; h = iptc_init("nat"); if(!h) { syslog(LOG_ERR, "get_redirect_rule() : " "iptc_init() failed : %s", iptc_strerror(errno)); return -1; } if(!iptc_is_chain(miniupnpd_nat_chain, h)) { syslog(LOG_ERR, "chain %s not found", miniupnpd_nat_chain); } else { #ifdef IPTABLES_143 for(e = iptc_first_rule(miniupnpd_nat_chain, h); e; e = iptc_next_rule(e, h)) #else for(e = iptc_first_rule(miniupnpd_nat_chain, &h); e; e = iptc_next_rule(e, &h)) #endif { if(proto==e->ip.proto) { match = (const struct ipt_entry_match *)&e->elems; if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN)) { const struct ipt_tcp * info; info = (const struct ipt_tcp *)match->data; if(eport != info->dpts[0]) continue; } else { const struct ipt_udp * info; info = (const struct ipt_udp *)match->data; if(eport != info->dpts[0]) continue; } target = (void *)e + e->target_offset; //target = ipt_get_target(e); mr = (const struct ip_nat_multi_range *)&target->data[0]; snprintip(iaddr, iaddrlen, ntohl(mr->range[0].min_ip)); *iport = ntohs(mr->range[0].min.all); /*if(desc) strncpy(desc, "miniupnpd", desclen);*/ get_redirect_desc(eport, proto, desc, desclen); if(packets) *packets = e->counters.pcnt; if(bytes) *bytes = e->counters.bcnt; r = 0; break; } } } if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif return r; } /* get_redirect_rule_by_index() * return -1 when the rule was not found */ int get_redirect_rule_by_index(int index, char * ifname, unsigned short * eport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, u_int64_t * packets, u_int64_t * bytes) { int r = -1; r = get_redirect_desc_by_index(index, eport, proto, desc, desclen); if (r==0) r = get_redirect_rule(ifname, *eport, *proto, iaddr, iaddrlen, iport, 0, 0, packets, bytes); #if 0 int i = 0; IPTC_HANDLE h; const struct ipt_entry * e; const struct ipt_entry_target * target; const struct ip_nat_multi_range * mr; const struct ipt_entry_match *match; h = iptc_init("nat"); if(!h) { syslog(LOG_ERR, "get_redirect_rule_by_index() : " "iptc_init() failed : %s", iptc_strerror(errno)); return -1; } if(!iptc_is_chain(miniupnpd_nat_chain, h)) { syslog(LOG_ERR, "chain %s not found", miniupnpd_nat_chain); } else { #ifdef IPTABLES_143 for(e = iptc_first_rule(miniupnpd_nat_chain, h); e; e = iptc_next_rule(e, h)) #else for(e = iptc_first_rule(miniupnpd_nat_chain, &h); e; e = iptc_next_rule(e, &h)) #endif { if(i==index) { *proto = e->ip.proto; match = (const struct ipt_entry_match *)&e->elems; if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN)) { const struct ipt_tcp * info; info = (const struct ipt_tcp *)match->data; *eport = info->dpts[0]; } else { const struct ipt_udp * info; info = (const struct ipt_udp *)match->data; *eport = info->dpts[0]; } target = (void *)e + e->target_offset; mr = (const struct ip_nat_multi_range *)&target->data[0]; snprintip(iaddr, iaddrlen, ntohl(mr->range[0].min_ip)); *iport = ntohs(mr->range[0].min.all); /*if(desc) strncpy(desc, "miniupnpd", desclen);*/ get_redirect_desc(*eport, *proto, desc, desclen); if(packets) *packets = e->counters.pcnt; if(bytes) *bytes = e->counters.bcnt; r = 0; break; } i++; } } if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif #endif /*0*/ return r; } /* delete_rule_and_commit() : * subfunction used in delete_redirect_and_filter_rules() */ int delete_rule_and_commit(const char * table, const char * miniupnpd_chain, unsigned short eport, int proto, const char * logcaller) { int r = -1; unsigned index = 0; unsigned i = 0; IPTC_HANDLE h; const struct ipt_entry * e; const struct ipt_entry_match *match; h = iptc_init(table); if(!h) { syslog(LOG_ERR, "get_index_rules() : " "iptc_init(%s) failed : %s", table, iptc_strerror(errno)); return -1; } if(!iptc_is_chain(miniupnpd_chain, h)) { syslog(LOG_ERR, "chain %s not found", miniupnpd_chain); } else { #ifdef IPTABLES_143 for(e = iptc_first_rule(miniupnpd_chain, h); e; e = iptc_next_rule(e, h), i++) #else for(e = iptc_first_rule(miniupnpd_chain, &h); e; e = iptc_next_rule(e, &h), i++) #endif { if(proto==e->ip.proto) { match = (const struct ipt_entry_match *)&e->elems; if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN)) { const struct ipt_tcp * info; info = (const struct ipt_tcp *)match->data; if(eport != info->dpts[0]) continue; } else { const struct ipt_udp * info; info = (const struct ipt_udp *)match->data; if(eport != info->dpts[0]) continue; } r = 0; index = i; break; } } } if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif if ((r == 0) && (h = iptc_init(table))) { syslog(LOG_INFO, "Trying to delete rules at index %u", index); /* Now delete both rules */ #ifdef IPTABLES_143 if(!iptc_delete_num_entry(miniupnpd_chain, index, h)) #else if(!iptc_delete_num_entry(miniupnpd_chain, index, &h)) #endif { syslog(LOG_ERR, "%s() : iptc_delete_num_entry(): %s\n", logcaller, iptc_strerror(errno)); r = -1; } #ifdef IPTABLES_143 else if(!iptc_commit(h)) #else else if(!iptc_commit(&h)) #endif { syslog(LOG_ERR, "%s() : iptc_commit(): %s\n", logcaller, iptc_strerror(errno)); r = -1; } if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif } return r; } /* delete_redirect_and_filter_rules() */ int delete_redirect_and_filter_rules(unsigned short eport, int proto) { int r = -1; if ((r = delete_rule_and_commit("nat", miniupnpd_nat_chain, eport, proto, "delete_redirect_rule") && delete_rule_and_commit("filter", miniupnpd_forward_chain, eport, proto, "delete_filter_rule")) == 0) del_redirect_desc(eport, proto); return r; } /* ==================================== */ /* TODO : add the -m state --state NEW,ESTABLISHED,RELATED * only for the filter rule */ static struct ipt_entry_match * get_tcp_match(unsigned short dport) { struct ipt_entry_match *match; struct ipt_tcp * tcpinfo; size_t size; size = IPT_ALIGN(sizeof(struct ipt_entry_match)) + IPT_ALIGN(sizeof(struct ipt_tcp)); match = calloc(1, size); match->u.match_size = size; strncpy(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN); tcpinfo = (struct ipt_tcp *)match->data; tcpinfo->spts[0] = 0; /* all source ports */ tcpinfo->spts[1] = 0xFFFF; tcpinfo->dpts[0] = dport; /* specified destination port */ tcpinfo->dpts[1] = dport; return match; } static struct ipt_entry_match * get_udp_match(unsigned short dport) { struct ipt_entry_match *match; struct ipt_udp * udpinfo; size_t size; size = IPT_ALIGN(sizeof(struct ipt_entry_match)) + IPT_ALIGN(sizeof(struct ipt_udp)); match = calloc(1, size); match->u.match_size = size; strncpy(match->u.user.name, "udp", IPT_FUNCTION_MAXNAMELEN); udpinfo = (struct ipt_udp *)match->data; udpinfo->spts[0] = 0; /* all source ports */ udpinfo->spts[1] = 0xFFFF; udpinfo->dpts[0] = dport; /* specified destination port */ udpinfo->dpts[1] = dport; return match; } static struct ipt_entry_target * get_dnat_target(const char * daddr, unsigned short dport) { struct ipt_entry_target * target; struct ip_nat_multi_range * mr; struct ip_nat_range * range; size_t size; size = IPT_ALIGN(sizeof(struct ipt_entry_target)) + IPT_ALIGN(sizeof(struct ip_nat_multi_range)); target = calloc(1, size); target->u.target_size = size; strncpy(target->u.user.name, "DNAT", IPT_FUNCTION_MAXNAMELEN); /* one ip_nat_range already included in ip_nat_multi_range */ mr = (struct ip_nat_multi_range *)&target->data[0]; mr->rangesize = 1; range = &mr->range[0]; range->min_ip = range->max_ip = inet_addr(daddr); range->flags |= IP_NAT_RANGE_MAP_IPS; range->min.all = range->max.all = htons(dport); range->flags |= IP_NAT_RANGE_PROTO_SPECIFIED; return target; } /* iptc_init_verify_and_append() * return 0 on success, -1 on failure */ static int iptc_init_verify_and_append(const char * table, const char * miniupnpd_chain, struct ipt_entry * e, const char * logcaller) { IPTC_HANDLE h; h = iptc_init(table); if(!h) { syslog(LOG_ERR, "%s : iptc_init() error : %s\n", logcaller, iptc_strerror(errno)); return -1; } if(!iptc_is_chain(miniupnpd_chain, h)) { syslog(LOG_ERR, "%s : iptc_is_chain() error : %s\n", logcaller, iptc_strerror(errno)); if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif return -1; } /* iptc_insert_entry(miniupnpd_chain, e, n, h/&h) could also be used */ #ifdef IPTABLES_143 if(!iptc_append_entry(miniupnpd_chain, e, h)) #else if(!iptc_append_entry(miniupnpd_chain, e, &h)) #endif { syslog(LOG_ERR, "%s : iptc_append_entry() error : %s\n", logcaller, iptc_strerror(errno)); if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif return -1; } #ifdef IPTABLES_143 if(!iptc_commit(h)) #else if(!iptc_commit(&h)) #endif { syslog(LOG_ERR, "%s : iptc_commit() error : %s\n", logcaller, iptc_strerror(errno)); if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif return -1; } if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif return 0; } /* add nat rule * iptables -t nat -A MINIUPNPD -p proto --dport eport -j DNAT --to iaddr:iport * */ int addnatrule(int proto, unsigned short eport, const char * iaddr, unsigned short iport) { int r = 0; struct ipt_entry * e; struct ipt_entry_match *match = NULL; struct ipt_entry_target *target = NULL; e = calloc(1, sizeof(struct ipt_entry)); e->ip.proto = proto; if(proto == IPPROTO_TCP) { match = get_tcp_match(eport); } else { match = get_udp_match(eport); } e->nfcache = NFC_IP_DST_PT; target = get_dnat_target(iaddr, iport); e->nfcache |= NFC_UNKNOWN; e = realloc(e, sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size); memcpy(e->elems, match, match->u.match_size); memcpy(e->elems + match->u.match_size, target, target->u.target_size); e->target_offset = sizeof(struct ipt_entry) + match->u.match_size; e->next_offset = sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size; r = iptc_init_verify_and_append("nat", miniupnpd_nat_chain, e, "addnatrule()"); free(target); free(match); free(e); return r; } /* ================================= */ static struct ipt_entry_target * get_accept_target(void) { struct ipt_entry_target * target = NULL; size_t size; size = IPT_ALIGN(sizeof(struct ipt_entry_target)) + IPT_ALIGN(sizeof(int)); target = calloc(1, size); target->u.user.target_size = size; strncpy(target->u.user.name, "ACCEPT", IPT_FUNCTION_MAXNAMELEN); return target; } /* add_filter_rule() * */ int add_filter_rule(int proto, const char * iaddr, unsigned short iport) { int r = 0; struct ipt_entry * e; struct ipt_entry_match *match = NULL; struct ipt_entry_target *target = NULL; e = calloc(1, sizeof(struct ipt_entry)); e->ip.proto = proto; if(proto == IPPROTO_TCP) { match = get_tcp_match(iport); } else { match = get_udp_match(iport); } e->nfcache = NFC_IP_DST_PT; e->ip.dst.s_addr = inet_addr(iaddr); e->ip.dmsk.s_addr = INADDR_NONE; target = get_accept_target(); e->nfcache |= NFC_UNKNOWN; e = realloc(e, sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size); memcpy(e->elems, match, match->u.match_size); memcpy(e->elems + match->u.match_size, target, target->u.target_size); e->target_offset = sizeof(struct ipt_entry) + match->u.match_size; e->next_offset = sizeof(struct ipt_entry) + match->u.match_size + target->u.target_size; r = iptc_init_verify_and_append("filter", miniupnpd_forward_chain, e, "add_filter_rule()"); free(target); free(match); free(e); return r; } /* ================================ */ static int print_match(const struct ipt_entry_match *match) { printf("match %s\n", match->u.user.name); if(0 == strncmp(match->u.user.name, "tcp", IPT_FUNCTION_MAXNAMELEN)) { struct ipt_tcp * tcpinfo; tcpinfo = (struct ipt_tcp *)match->data; printf("srcport = %hu:%hu dstport = %hu:%hu\n", tcpinfo->spts[0], tcpinfo->spts[1], tcpinfo->dpts[0], tcpinfo->dpts[1]); } else if(0 == strncmp(match->u.user.name, "udp", IPT_FUNCTION_MAXNAMELEN)) { struct ipt_udp * udpinfo; udpinfo = (struct ipt_udp *)match->data; printf("srcport = %hu:%hu dstport = %hu:%hu\n", udpinfo->spts[0], udpinfo->spts[1], udpinfo->dpts[0], udpinfo->dpts[1]); } return 0; } static void print_iface(const char * iface, const unsigned char * mask, int invert) { unsigned i; if(mask[0] == 0) return; if(invert) printf("! "); for(i=0; i> 24, (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff); } /* for debug */ /* read the "filter" and "nat" tables */ int list_redirect_rule(const char * ifname) { IPTC_HANDLE h; const struct ipt_entry * e; const struct ipt_entry_target * target; const struct ip_nat_multi_range * mr; const char * target_str; h = iptc_init("nat"); if(!h) { printf("iptc_init() error : %s\n", iptc_strerror(errno)); return -1; } if(!iptc_is_chain(miniupnpd_nat_chain, h)) { printf("chain %s not found\n", miniupnpd_nat_chain); #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif return -1; } #ifdef IPTABLES_143 for(e = iptc_first_rule(miniupnpd_nat_chain, h); e; e = iptc_next_rule(e, h)) { target_str = iptc_get_target(e, h); #else for(e = iptc_first_rule(miniupnpd_nat_chain, &h); e; e = iptc_next_rule(e, &h)) { target_str = iptc_get_target(e, &h); #endif printf("===\n"); printf("src = %s%s/%s\n", (e->ip.invflags & IPT_INV_SRCIP)?"! ":"", inet_ntoa(e->ip.src), inet_ntoa(e->ip.smsk)); printf("dst = %s%s/%s\n", (e->ip.invflags & IPT_INV_DSTIP)?"! ":"", inet_ntoa(e->ip.dst), inet_ntoa(e->ip.dmsk)); /*printf("in_if = %s out_if = %s\n", e->ip.iniface, e->ip.outiface);*/ printf("in_if = "); print_iface(e->ip.iniface, e->ip.iniface_mask, e->ip.invflags & IPT_INV_VIA_IN); printf(" out_if = "); print_iface(e->ip.outiface, e->ip.outiface_mask, e->ip.invflags & IPT_INV_VIA_OUT); printf("\n"); printf("ip.proto = %s%d\n", (e->ip.invflags & IPT_INV_PROTO)?"! ":"", e->ip.proto); /* display matches stuff */ if(e->target_offset) { IPT_MATCH_ITERATE(e, print_match); /*printf("\n");*/ } printf("target = %s\n", target_str); target = (void *)e + e->target_offset; mr = (const struct ip_nat_multi_range *)&target->data[0]; printf("ips "); printip(ntohl(mr->range[0].min_ip)); printf(" "); printip(ntohl(mr->range[0].max_ip)); printf("\nports %hu %hu\n", ntohs(mr->range[0].min.all), ntohs(mr->range[0].max.all)); printf("flags = %x\n", mr->range[0].flags); } if(h) #ifdef IPTABLES_143 iptc_free(h); #else iptc_free(&h); #endif return 0; }