void handle_ip(const struct arguments *args, const uint8_t *pkt, const size_t length, const int epoll_fd, int sessions, int maxsessions) { uint8_t protocol; void *saddr; void *daddr; char source[INET6_ADDRSTRLEN + 1]; char dest[INET6_ADDRSTRLEN + 1]; char flags[10]; int flen = 0; uint8_t *payload; // Get protocol, addresses & payload uint8_t version = (*pkt) >> 4; if (version == 4) { if (length < sizeof(struct iphdr)) { log_android(ANDROID_LOG_WARN, "IP4 packet too short length %d", length); return; } struct iphdr *ip4hdr = (struct iphdr *) pkt; protocol = ip4hdr->protocol; saddr = &ip4hdr->saddr; daddr = &ip4hdr->daddr; if (ip4hdr->frag_off & IP_MF) { log_android(ANDROID_LOG_ERROR, "IP fragment offset %u", (ip4hdr->frag_off & IP_OFFMASK) * 8); return; } uint8_t ipoptlen = (uint8_t) ((ip4hdr->ihl - 5) * 4); payload = (uint8_t *) (pkt + sizeof(struct iphdr) + ipoptlen); if (ntohs(ip4hdr->tot_len) != length) { log_android(ANDROID_LOG_ERROR, "Invalid length %u header length %u", length, ntohs(ip4hdr->tot_len)); return; } if (loglevel < ANDROID_LOG_WARN) { if (!calc_checksum(0, (uint8_t *) ip4hdr, sizeof(struct iphdr))) { log_android(ANDROID_LOG_ERROR, "Invalid IP checksum"); return; } } } else if (version == 6) { if (length < sizeof(struct ip6_hdr)) { log_android(ANDROID_LOG_WARN, "IP6 packet too short length %d", length); return; } struct ip6_hdr *ip6hdr = (struct ip6_hdr *) pkt; // Skip extension headers uint16_t off = 0; protocol = ip6hdr->ip6_nxt; if (!is_upper_layer(protocol)) { log_android(ANDROID_LOG_WARN, "IP6 extension %d", protocol); off = sizeof(struct ip6_hdr); struct ip6_ext *ext = (struct ip6_ext *) (pkt + off); while (is_lower_layer(ext->ip6e_nxt) && !is_upper_layer(protocol)) { protocol = ext->ip6e_nxt; log_android(ANDROID_LOG_WARN, "IP6 extension %d", protocol); off += (8 + ext->ip6e_len); ext = (struct ip6_ext *) (pkt + off); } if (!is_upper_layer(protocol)) { off = 0; protocol = ip6hdr->ip6_nxt; log_android(ANDROID_LOG_WARN, "IP6 final extension %d", protocol); } } saddr = &ip6hdr->ip6_src; daddr = &ip6hdr->ip6_dst; payload = (uint8_t *) (pkt + sizeof(struct ip6_hdr) + off); // TODO checksum } else { log_android(ANDROID_LOG_ERROR, "Unknown version %d", version); return; } inet_ntop(version == 4 ? AF_INET : AF_INET6, saddr, source, sizeof(source)); inet_ntop(version == 4 ? AF_INET : AF_INET6, daddr, dest, sizeof(dest)); // Get ports & flags int syn = 0; uint16_t sport = 0; uint16_t dport = 0; if (protocol == IPPROTO_ICMP || protocol == IPPROTO_ICMPV6) { if (length - (payload - pkt) < sizeof(struct icmp)) { log_android(ANDROID_LOG_WARN, "ICMP packet too short"); return; } struct icmp *icmp = (struct icmp *) payload; // http://lwn.net/Articles/443051/ sport = ntohs(icmp->icmp_id); dport = ntohs(icmp->icmp_id); } else if (protocol == IPPROTO_UDP) { if (length - (payload - pkt) < sizeof(struct udphdr)) { log_android(ANDROID_LOG_WARN, "UDP packet too short"); return; } struct udphdr *udp = (struct udphdr *) payload; sport = ntohs(udp->source); dport = ntohs(udp->dest); // TODO checksum (IPv6) } else if (protocol == IPPROTO_TCP) { if (length - (payload - pkt) < sizeof(struct tcphdr)) { log_android(ANDROID_LOG_WARN, "TCP packet too short"); return; } struct tcphdr *tcp = (struct tcphdr *) payload; sport = ntohs(tcp->source); dport = ntohs(tcp->dest); if (tcp->syn) { syn = 1; flags[flen++] = 'S'; } if (tcp->ack) flags[flen++] = 'A'; if (tcp->psh) flags[flen++] = 'P'; if (tcp->fin) flags[flen++] = 'F'; if (tcp->rst) flags[flen++] = 'R'; // TODO checksum } else if (protocol != IPPROTO_HOPOPTS && protocol != IPPROTO_IGMP && protocol != IPPROTO_ESP) report_error(args, 1, "Unknown protocol %d", protocol); flags[flen] = 0; // Limit number of sessions if (sessions >= maxsessions) { if ((protocol == IPPROTO_ICMP || protocol == IPPROTO_ICMPV6) || (protocol == IPPROTO_UDP && !has_udp_session(args, pkt, payload)) || (protocol == IPPROTO_TCP && syn)) { log_android(ANDROID_LOG_ERROR, "%d of max %d sessions, dropping version %d protocol %d", sessions, maxsessions, protocol, version); return; } } // Get uid jint uid = -1; if (protocol == IPPROTO_ICMP || protocol == IPPROTO_ICMPV6 || (protocol == IPPROTO_UDP && !has_udp_session(args, pkt, payload)) || (protocol == IPPROTO_TCP && syn)) uid = get_uid_retry(version, protocol, saddr, sport); log_android(ANDROID_LOG_DEBUG, "Packet v%d %s/%u > %s/%u proto %d flags %s uid %d", version, source, sport, dest, dport, protocol, flags, uid); // Check if allowed int allowed = 0; struct allowed *redirect = NULL; if (protocol == IPPROTO_UDP && has_udp_session(args, pkt, payload)) allowed = 1; // could be a lingering/blocked session else if (protocol == IPPROTO_TCP && !syn) allowed = 1; // assume existing session else { jobject objPacket = create_packet( args, version, protocol, flags, source, sport, dest, dport, "", uid, 0); redirect = is_address_allowed(args, objPacket); allowed = (redirect != NULL); if (redirect != NULL && (*redirect->raddr == 0 || redirect->rport == 0)) redirect = NULL; } // Handle allowed traffic if (allowed) { if (protocol == IPPROTO_ICMP || protocol == IPPROTO_ICMPV6) handle_icmp(args, pkt, length, payload, uid, epoll_fd); else if (protocol == IPPROTO_UDP) handle_udp(args, pkt, length, payload, uid, redirect, epoll_fd); else if (protocol == IPPROTO_TCP) handle_tcp(args, pkt, length, payload, uid, redirect, epoll_fd); } else { if (protocol == IPPROTO_UDP) block_udp(args, pkt, length, payload, uid); log_android(ANDROID_LOG_WARN, "Address v%d p%d %s/%u syn %d not allowed", version, protocol, dest, dport, syn); } }
void check_allowed(const struct arguments *args) { char source[INET6_ADDRSTRLEN + 1]; char dest[INET6_ADDRSTRLEN + 1]; struct icmp_session *i = icmp_session; while (i != NULL) { if (!i->stop) { if (i->version == 4) { inet_ntop(AF_INET, &i->saddr.ip4, source, sizeof(source)); inet_ntop(AF_INET, &i->daddr.ip4, dest, sizeof(dest)); } else { inet_ntop(AF_INET6, &i->saddr.ip6, source, sizeof(source)); inet_ntop(AF_INET6, &i->daddr.ip6, dest, sizeof(dest)); } jobject objPacket = create_packet( args, i->version, IPPROTO_ICMP, "", source, 0, dest, 0, "", i->uid, 0); if (is_address_allowed(args, objPacket) == NULL) { i->stop = 1; log_android(ANDROID_LOG_WARN, "ICMP terminate %d uid %d", i->socket, i->uid); } } i = i->next; } struct udp_session *l = NULL; struct udp_session *u = udp_session; while (u != NULL) { if (u->state == UDP_ACTIVE) { if (u->version == 4) { inet_ntop(AF_INET, &u->saddr.ip4, source, sizeof(source)); inet_ntop(AF_INET, &u->daddr.ip4, dest, sizeof(dest)); } else { inet_ntop(AF_INET6, &u->saddr.ip6, source, sizeof(source)); inet_ntop(AF_INET6, &u->daddr.ip6, dest, sizeof(dest)); } jobject objPacket = create_packet( args, u->version, IPPROTO_UDP, "", source, ntohs(u->source), dest, ntohs(u->dest), "", u->uid, 0); if (is_address_allowed(args, objPacket) == NULL) { u->state = UDP_FINISHING; log_android(ANDROID_LOG_WARN, "UDP terminate session socket %d uid %d", u->socket, u->uid); } } else if (u->state == UDP_BLOCKED) { log_android(ANDROID_LOG_WARN, "UDP remove blocked session uid %d", u->uid); if (l == NULL) udp_session = u->next; else l->next = u->next; struct udp_session *c = u; u = u->next; free(c); continue; } l = u; u = u->next; } struct tcp_session *t = tcp_session; while (t != NULL) { if (t->state != TCP_CLOSING && t->state != TCP_CLOSE) { if (t->version == 4) { inet_ntop(AF_INET, &t->saddr.ip4, source, sizeof(source)); inet_ntop(AF_INET, &t->daddr.ip4, dest, sizeof(dest)); } else { inet_ntop(AF_INET6, &t->saddr.ip6, source, sizeof(source)); inet_ntop(AF_INET6, &t->daddr.ip6, dest, sizeof(dest)); } jobject objPacket = create_packet( args, t->version, IPPROTO_TCP, "", source, ntohs(t->source), dest, ntohs(t->dest), "", t->uid, 0); if (is_address_allowed(args, objPacket) == NULL) { write_rst(args, t); log_android(ANDROID_LOG_WARN, "TCP terminate socket %d uid %d", t->socket, t->uid); } } t = t->next; } }