blob: b8863e449e1d6a076ea3f2c40a06871ce4d25c03 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "portier/nd_bpf.h"
#include <limits.h>
#include <linux/filter.h>
#include <net/ethernet.h>
#include <netinet/icmp6.h>
#include <netinet/ip6.h>
#include <stddef.h>
namespace portier {
namespace {
// Outgoing hop-limit of proxied IPv6 packets. Required by RFC 4389 to
// prevent receivers from dropping what may appear to be forwarded packets.
constexpr uint8_t kProxiedHopLimit = 255;
// This constant is used by BPF programs to indicate that the entire packet
// should be returned to user-space.
constexpr uint32_t kEntirePacket = INT_MAX;
// BPF notes:
// BPF_STMT(code, k)
// BPF_JUMP(code, k, j true, j false)
//
// The return value of a BPF program is the number of bytes of the packet
// that should be passed to the user-space program. Zero is a special
// value in that the packet is simply dropped, and the user-space socket
// is never notified of the packet. Returning a number larger than the
// actaul length of the packet will cause the entire packet to be passed
// to the user-space program.
//
// Any out of bounds exceptions, illegal OPs code, divide by zero, and other
// possible errors cause the BPF program to exit as if it had returned zero.
// This is a desired feature as the BPF program can be made to assume the
// whole packet is received without doing any bound checks. If the packet
// was corrupted or truncated during transit, then the BPF will drop it
// when accessing out of bound data.
// BPF for ICMPv6 Neighbor Solicitation.
// Algorithm:
// ether_header = packet_buf; // Start of frame.
// if (ether_header->ether_type != IPv6) {
// return 0;
// }
// ip6_hdr = ether_header + sizeof(struct ether_header); // IPv6 header
// if (ip6_hdr->ip6_nxt != ICMPv6 || ip6_hdr->ip6_hops != 255) {
// return 0;
// }
// icmp6_hdr = ip6_hdr + sizeof(struct ip6_hdr); // ICMPv6 header
// if (icmp6_hdr->icmp6_type != NS &&
// icmp6_hdr->icmp6_type != NA &&
// icmp6_hdr->icmp6_type != RA &&
// icmp6_hdr->icmp6_type != R) {
// return 0;
// }
// if (icmp6_hdr->icmp6_code != 0) {
// return 0;
// }
// return MAX;
struct sock_filter kNeighborDiscoveryFilterInstructions[] = {
// Load ethernet type (16-bits, H).
BPF_STMT(BPF_LD | BPF_H | BPF_ABS,
offsetof(struct ether_header, ether_type)),
// Check if it equals IPv6, skip next if true.
BPF_JUMP(BPF_JMP | BPF_JEQ, ETHERTYPE_IPV6, 1, 0),
// Return 0.
BPF_STMT(BPF_RET | BPF_K, 0),
// Move index to start of IPv6 header.
BPF_STMT(BPF_LDX | BPF_IMM, sizeof(struct ether_header)),
// Load IPv6 next header (8-bits, B).
BPF_STMT(BPF_LD | BPF_B | BPF_IND, offsetof(struct ip6_hdr, ip6_nxt)),
// Check if equals ICMPv6, if not, then goto return 0.
BPF_JUMP(BPF_JMP | BPF_JEQ, IPPROTO_ICMPV6, 0, 2),
// Load IPv6 hop limit (8-bit, B).
BPF_STMT(BPF_LD | BPF_B | BPF_IND, offsetof(struct ip6_hdr, ip6_hops)),
// Check if equal to 255, skip return if true.
BPF_JUMP(BPF_JMP | BPF_JEQ, kProxiedHopLimit, 1, 0),
// Return 0.
BPF_STMT(BPF_RET | BPF_K, 0),
// Move index to start of ICMPv6 header.
BPF_STMT(BPF_MISC | BPF_TXA, 0),
BPF_STMT(BPF_ALU | BPF_ADD | BPF_IMM, sizeof(struct ip6_hdr)),
BPF_STMT(BPF_MISC | BPF_TAX, 0),
// Load ICMPv6 type (8-bits, B).
BPF_STMT(BPF_LD | BPF_B | BPF_IND, offsetof(struct icmp6_hdr, icmp6_type)),
// Check if is ND ICMPv6 message.
BPF_JUMP(BPF_JMP | BPF_JEQ, ND_ROUTER_ADVERT, 4, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ, ND_NEIGHBOR_SOLICIT, 3, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ, ND_NEIGHBOR_ADVERT, 2, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ, ND_REDIRECT, 1, 0),
// Return 0.
BPF_STMT(BPF_RET | BPF_K, 0),
// Load ICMPv6 code (8-bit, B).
BPF_STMT(BPF_LD | BPF_B | BPF_IND, offsetof(struct icmp6_hdr, icmp6_code)),
// Check if is code 0.
BPF_JUMP(BPF_JMP | BPF_JEQ, 0, 1, 0),
// Return 0.
BPF_STMT(BPF_RET | BPF_K, 0),
// Return MAX.
BPF_STMT(BPF_RET | BPF_K, kEntirePacket),
};
// BPF for IPv6 packets, other than ICMPv6 Neighbor Discovery.
// Algorithm is the similar to ND Filter above, except that it returns
// max if the IPv6 packet is not a one of the ND Messages that require
// special proxying rules.
struct sock_filter kNonNDFilterInstructions[] = {
// Load ethernet type (16-bits, H).
BPF_STMT(BPF_LD | BPF_H | BPF_ABS,
offsetof(struct ether_header, ether_type)),
// Check if it equals IPv6, skip next if true.
BPF_JUMP(BPF_JMP | BPF_JEQ, ETHERTYPE_IPV6, 1, 0),
// Return 0.
BPF_STMT(BPF_RET | BPF_K, 0),
// Move index to start of IPv6 header.
BPF_STMT(BPF_LDX | BPF_IMM, sizeof(struct ether_header)),
// Load IPv6 next header (8-bits, B).
BPF_STMT(BPF_LD | BPF_B | BPF_IND, offsetof(struct ip6_hdr, ip6_nxt)),
// Check if equals ICMPv6, if not, then return max.
BPF_JUMP(BPF_JMP | BPF_JEQ, IPPROTO_ICMPV6, 1, 0),
// Return MAX.
BPF_STMT(BPF_RET | BPF_K, kEntirePacket),
// Move index to start of ICMPv6 header.
BPF_STMT(BPF_MISC | BPF_TXA, 0),
BPF_STMT(BPF_ALU | BPF_ADD | BPF_IMM, sizeof(struct ip6_hdr)),
BPF_STMT(BPF_MISC | BPF_TAX, 0),
// Load ICMPv6 type (8-bits, B).
BPF_STMT(BPF_LD | BPF_B | BPF_IND, offsetof(struct icmp6_hdr, icmp6_type)),
// Check if is ND ICMPv6 message.
BPF_JUMP(BPF_JMP | BPF_JEQ, ND_ROUTER_ADVERT, 4, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ, ND_NEIGHBOR_SOLICIT, 3, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ, ND_NEIGHBOR_ADVERT, 2, 0),
BPF_JUMP(BPF_JMP | BPF_JEQ, ND_REDIRECT, 1, 0),
// Return MAX.
BPF_STMT(BPF_RET | BPF_K, kEntirePacket),
// Return 0.
BPF_STMT(BPF_RET | BPF_K, 0),
};
} // namespace
const struct sock_fprog kNeighborDiscoveryFilter = {
sizeof(kNeighborDiscoveryFilterInstructions) / sizeof(struct sock_filter),
kNeighborDiscoveryFilterInstructions};
const struct sock_fprog kNonNeighborDiscoveryFilter = {
sizeof(kNonNDFilterInstructions) / sizeof(struct sock_filter),
kNonNDFilterInstructions};
} // namespace portier.