blob: 9989e6bd9e31dab0b643ece26dad7ce37d6e6131 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <linux/types.h>
#include <linux/bpf.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/if_ether.h>
#include <linux/pkt_cls.h>
#include <linux/tcp.h>
#include <stdint.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <string.h>
#include "bpf_helpers.h"
#define MAX_POLICIES 16
#define MAP_A 1
#define MAP_B 2
#define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
// TODO: these are already defined in /system/netd/bpf_progs/bpf_net_helpers.h
// should they be moved to common location?
static uint64_t (*bpf_get_socket_cookie)(struct __sk_buff* skb) =
(void*)BPF_FUNC_get_socket_cookie;
static int (*bpf_skb_store_bytes)(struct __sk_buff* skb, __u32 offset, const void* from, __u32 len,
__u64 flags) = (void*)BPF_FUNC_skb_store_bytes;
static int (*bpf_l3_csum_replace)(struct __sk_buff* skb, __u32 offset, __u64 from, __u64 to,
__u64 flags) = (void*)BPF_FUNC_l3_csum_replace;
typedef struct {
// Add family here to match __sk_buff ?
struct in_addr srcIp;
struct in_addr dstIp;
__be16 srcPort;
__be16 dstPort;
uint8_t proto;
uint8_t dscpVal;
uint8_t pad[2];
} Ipv4RuleEntry;
STRUCT_SIZE(Ipv4RuleEntry, 2 * 4 + 2 * 2 + 2 * 1 + 2); // 16, 4 for in_addr
#define SRC_IP_MASK 1
#define DST_IP_MASK 2
#define SRC_PORT_MASK 4
#define DST_PORT_MASK 8
#define PROTO_MASK 16
typedef struct {
struct in6_addr srcIp;
struct in6_addr dstIp;
__be16 srcPort;
__be16 dstPortStart;
__be16 dstPortEnd;
uint8_t proto;
uint8_t dscpVal;
uint8_t mask;
uint8_t pad[3];
} Ipv4Policy;
STRUCT_SIZE(Ipv4Policy, 2 * 16 + 3 * 2 + 3 * 1 + 3); // 44
typedef struct {
struct in6_addr srcIp;
struct in6_addr dstIp;
__be16 srcPort;
__be16 dstPortStart;
__be16 dstPortEnd;
uint8_t proto;
uint8_t dscpVal;
uint8_t mask;
// should we override this struct to include the param bitmask for linear search?
// For mapping socket to policies, all the params should match exactly since we can
// pull any missing from the sock itself.
} Ipv6RuleEntry;
STRUCT_SIZE(Ipv6RuleEntry, 2 * 16 + 3 * 2 + 3 * 1 + 3); // 44
// TODO: move to using 1 map. Map v4 address to 0xffff::v4
DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_A, HASH, uint64_t, Ipv4RuleEntry, MAX_POLICIES,
AID_SYSTEM)
DEFINE_BPF_MAP_GRW(ipv4_socket_to_policies_map_B, HASH, uint64_t, Ipv4RuleEntry, MAX_POLICIES,
AID_SYSTEM)
DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_A, HASH, uint64_t, Ipv6RuleEntry, MAX_POLICIES,
AID_SYSTEM)
DEFINE_BPF_MAP_GRW(ipv6_socket_to_policies_map_B, HASH, uint64_t, Ipv6RuleEntry, MAX_POLICIES,
AID_SYSTEM)
DEFINE_BPF_MAP_GRW(switch_comp_map, ARRAY, int, uint64_t, 1, AID_SYSTEM)
DEFINE_BPF_MAP_GRW(ipv4_dscp_policies_map, ARRAY, uint32_t, Ipv4Policy, MAX_POLICIES,
AID_SYSTEM)
DEFINE_BPF_MAP_GRW(ipv6_dscp_policies_map, ARRAY, uint32_t, Ipv6RuleEntry, MAX_POLICIES,
AID_SYSTEM)
DEFINE_BPF_PROG_KVER("schedcls/set_dscp", AID_ROOT, AID_SYSTEM,
schedcls_set_dscp, KVER(5, 4, 0))
(struct __sk_buff* skb) {
int one = 0;
uint64_t* selectedMap = bpf_switch_comp_map_lookup_elem(&one);
// use this with HASH map so map lookup only happens once policies have been added?
if (!selectedMap) {
return TC_ACT_PIPE;
}
// used for map lookup
uint64_t cookie = bpf_get_socket_cookie(skb);
// Do we need separate maps for ipv4/ipv6
if (skb->protocol == htons(ETH_P_IP)) { //maybe bpf_htons()
Ipv4RuleEntry* v4Policy;
if (*selectedMap == MAP_A) {
v4Policy = bpf_ipv4_socket_to_policies_map_A_lookup_elem(&cookie);
} else {
v4Policy = bpf_ipv4_socket_to_policies_map_B_lookup_elem(&cookie);
}
// How to use bitmask here to compare params efficiently?
// TODO: add BPF_PROG_TYPE_SK_SKB prog type to Loader?
void* data = (void*)(long)skb->data;
const void* data_end = (void*)(long)skb->data_end;
const struct iphdr* const iph = data;
// Must have ipv4 header
if (data + sizeof(*iph) > data_end) return TC_ACT_PIPE;
// IP version must be 4
if (iph->version != 4) return TC_ACT_PIPE;
// We cannot handle IP options, just standard 20 byte == 5 dword minimal IPv4 header
if (iph->ihl != 5) return TC_ACT_PIPE;
if (iph->protocol != IPPROTO_UDP) return TC_ACT_PIPE;
struct udphdr *udp;
udp = data + sizeof(struct iphdr); //sizeof(struct ethhdr)
if ((void*)(udp + 1) > data_end) return TC_ACT_PIPE;
// Source/destination port in udphdr are stored in be16, need to convert to le16.
// This can be done via ntohs or htons. Is there a more preferred way?
// Cached policy was found.
if (v4Policy && iph->saddr == v4Policy->srcIp.s_addr &&
iph->daddr == v4Policy->dstIp.s_addr &&
ntohs(udp->source) == v4Policy->srcPort &&
ntohs(udp->dest) == v4Policy->dstPort &&
iph->protocol == v4Policy->proto) {
// set dscpVal in packet. Least sig 2 bits of TOS
// reference ipv4_change_dsfield()
// TODO: fix checksum...
int ecn = iph->tos & 3;
uint8_t newDscpVal = (v4Policy->dscpVal << 2) + ecn;
int oldDscpVal = iph->tos >> 2;
bpf_l3_csum_replace(skb, 1, oldDscpVal, newDscpVal, sizeof(uint8_t));
bpf_skb_store_bytes(skb, 1, &newDscpVal, sizeof(uint8_t), 0);
return TC_ACT_PIPE;
}
// linear scan ipv4_dscp_policies_map, stored socket params do not match actual
int bestScore = -1;
uint32_t bestMatch = 0;
for (register uint64_t i = 0; i < MAX_POLICIES; i++) {
int score = 0;
uint8_t tempMask = 0;
// Using a uint62 in for loop prevents infinite loop during BPF load,
// but the key is uint32, so convert back.
uint32_t key = i;
Ipv4Policy* policy = bpf_ipv4_dscp_policies_map_lookup_elem(&key);
// if mask is 0 continue, key does not have corresponding policy value
if (policy && policy->mask != 0) {
if ((policy->mask & SRC_IP_MASK) == SRC_IP_MASK &&
iph->saddr == policy->srcIp.s6_addr32[3]) {
score++;
tempMask |= SRC_IP_MASK;
}
if ((policy->mask & DST_IP_MASK) == DST_IP_MASK &&
iph->daddr == policy->dstIp.s6_addr32[3]) {
score++;
tempMask |= DST_IP_MASK;
}
if ((policy->mask & SRC_PORT_MASK) == SRC_PORT_MASK &&
ntohs(udp->source) == htons(policy->srcPort)) {
score++;
tempMask |= SRC_PORT_MASK;
}
if ((policy->mask & DST_PORT_MASK) == DST_PORT_MASK &&
ntohs(udp->dest) >= htons(policy->dstPortStart) &&
ntohs(udp->dest) <= htons(policy->dstPortEnd)) {
score++;
tempMask |= DST_PORT_MASK;
}
if ((policy->mask & PROTO_MASK) == PROTO_MASK &&
iph->protocol == policy->proto) {
score++;
tempMask |= PROTO_MASK;
}
if (score > bestScore && tempMask == policy->mask) {
bestMatch = i;
bestScore = score;
}
}
}
uint8_t newDscpVal = 0; // Can 0 be used as default forwarding value?
uint8_t curDscp = iph->tos & 252;
if (bestScore > 0) {
Ipv4Policy* policy = bpf_ipv4_dscp_policies_map_lookup_elem(&bestMatch);
if (policy) {
// TODO: if DSCP value is already set ignore?
// TODO: update checksum, for testing increment counter...
int ecn = iph->tos & 3;
newDscpVal = (policy->dscpVal << 2) + ecn;
}
}
Ipv4RuleEntry value = {
.srcIp.s_addr = iph->saddr,
.dstIp.s_addr = iph->daddr,
.srcPort = udp->source,
.dstPort = udp->dest,
.proto = iph->protocol,
.dscpVal = newDscpVal,
};
if (!cookie)
return TC_ACT_PIPE;
// Update map
if (*selectedMap == MAP_A) {
bpf_ipv4_socket_to_policies_map_A_update_elem(&cookie, &value, BPF_ANY);
} else {
bpf_ipv4_socket_to_policies_map_B_update_elem(&cookie, &value, BPF_ANY);
}
// Need to store bytes after updating map or program will not load.
if (newDscpVal != curDscp) {
// 1 is the offset (Version/Header length)
int oldDscpVal = iph->tos >> 2;
bpf_l3_csum_replace(skb, 1, oldDscpVal, newDscpVal, sizeof(uint8_t));
bpf_skb_store_bytes(skb, 1, &newDscpVal, sizeof(uint8_t), 0);
}
} else if (skb->protocol == htons(ETH_P_IPV6)) { //maybe bpf_htons()
Ipv6RuleEntry* v6Policy;
if (*selectedMap == MAP_A) {
v6Policy = bpf_ipv6_socket_to_policies_map_A_lookup_elem(&cookie);
} else {
v6Policy = bpf_ipv6_socket_to_policies_map_B_lookup_elem(&cookie);
}
if (!v6Policy)
return TC_ACT_PIPE;
// TODO: Add code to process IPv6 packet.
}
// Always return TC_ACT_PIPE
return TC_ACT_PIPE;
}
LICENSE("Apache 2.0");
CRITICAL("Connectivity");