Commit 2ae2a53f authored by Brendan Gregg's avatar Brendan Gregg Committed by GitHub

Merge pull request #269 from dalehamel/inet-ntop

Initial inet_ntop implementation
parents 6668a961 b470700d
......@@ -268,6 +268,7 @@ Functions:
- `clear(@x)` - Delete all key/values from a map
- `sym(void *p)` - Resolve kernel address
- `usym(void *p)` - Resolve user space address
- `ntop(int af, int addr)` - Resolve ip address
- `kaddr(char *name)` - Resolve kernel symbol name
- `uaddr(char *name)` - Resolve user space symbol name
- `reg(char *name)` - Returns the value stored in the named register
......
......@@ -59,6 +59,7 @@ This is a work in progress. If something is missing, check the bpftrace source t
- [11. `system()`: System](#11-system-system)
- [12. `exit()`: Exit](#12-exit-exit)
- [13. `cgroupid()`: Resolve cgroup ID](#13-cgroupid-resolve-cgroup-id)
- [14. `ntop()`: Convert IP address data to text](#14-ntop-convert-ip-address-data-to-text)
- [Map Functions](#map-functions)
- [1. Builtins](#1-builtins-2)
- [2. `count()`: Count](#2-count-count)
......@@ -1386,6 +1387,40 @@ And in other terminal:
# cat /etc/shadow
```
## 14. `ntop`: Convert IP address data to text
Syntax: `ntop(int af, int addr)`
This returns the string representation of an IPv4 address. IPv6 support will be added in the future.
Examples:
A simple example of ntop with an ipv4 hex-encoded literal:
```
bpftrace -e 'BEGIN { $addr_type=2; printf("%s\n", ntop($addr_type, 0x0100007f));}'
127.0.0.1
^C
```
Note that the literal `2` above is the value of the enum `AF_INET`, and `10` would indicate `AF_INET6` once supported, as per `include/linux/socket.h`.
A less trivial example of this usage, tracing tcp outbound connections, and printing the destination address:
```
bpftrace -e '#include <net/sock.h>
kprobe:tcp_connect { $sk = ((sock *) arg0); printf("%s\n", ntop(2, $sk->__sk_common.skc_daddr)); }'
Attaching 1 probe...
169.254.0.1
^C
```
And initiate a connection to this (or any) address in another terminal:
```
curl 169.254.0.1
```
# Map Functions
Maps are special BPF data types that can be used to store counts, statistics, and histograms. They are also used for some variable types as discussed in the previous section, whenever `@` is used: [globals](#21-global), [per thread variables](#22-per-thread), and [associative arrays](#3--associative-arrays).
......
......@@ -408,6 +408,20 @@ void CodegenLLVM::visit(Call &call)
b_.CreateStore(pid, pid_offset);
expr_ = buf;
}
else if (call.func == "ntop")
{
// store uint64_t[2] with: [0]: (uint64_t)address_family, [1]: (uint64_t) inet_address
// To support ipv6, [2] should be the remaining bytes of the address
AllocaInst *buf = b_.CreateAllocaBPF(call.type, "inet");
b_.CreateMemSet(buf, b_.getInt8(0), call.type.size, 1);
Value *af_offset = b_.CreateGEP(buf, b_.getInt64(0));
Value *inet_offset = b_.CreateGEP(buf, {b_.getInt64(0), b_.getInt64(8)});
call.vargs->at(0)->accept(*this);
b_.CreateStore(expr_, af_offset);
call.vargs->at(1)->accept(*this);
b_.CreateStore(expr_, inet_offset);
expr_ = buf;
}
else if (call.func == "reg")
{
auto &reg_name = static_cast<String&>(*call.vargs->at(0)).str;
......@@ -1176,7 +1190,8 @@ AllocaInst *CodegenLLVM::getMapKey(Map &map)
for (Expression *expr : *map.vargs) {
expr->accept(*this);
Value *offset_val = b_.CreateGEP(key, {b_.getInt64(0), b_.getInt64(offset)});
if (expr->type.type == Type::string || expr->type.type == Type::usym)
if (expr->type.type == Type::string || expr->type.type == Type::usym ||
expr->type.type == Type::inet)
b_.CREATE_MEMCPY(offset_val, expr_, expr->type.size, 1);
else
b_.CreateStore(expr_, offset_val);
......@@ -1206,7 +1221,8 @@ AllocaInst *CodegenLLVM::getHistMapKey(Map &map, Value *log2)
for (Expression *expr : *map.vargs) {
expr->accept(*this);
Value *offset_val = b_.CreateGEP(key, {b_.getInt64(0), b_.getInt64(offset)});
if (expr->type.type == Type::string || expr->type.type == Type::usym)
if (expr->type.type == Type::string || expr->type.type == Type::usym ||
expr->type.type == Type::inet)
b_.CREATE_MEMCPY(offset_val, expr_, expr->type.size, 1);
else
b_.CreateStore(expr_, offset_val);
......
......@@ -213,6 +213,16 @@ void SemanticAnalyser::visit(Call &call)
else if (call.func == "usym")
call.type = SizedType(Type::usym, 16);
}
else if (call.func == "ntop") {
check_nargs(call, 2);
check_arg(call, Type::integer, 0);
check_arg(call, Type::integer, 1);
// 3 x 64 bit words are needed, hence 24 bytes
// 0 - To store address family, but stay word-aligned
// 1 - To store ipv4 or first half of ipv6
// 2 - Reserved for future use, to store remainder of ipv6
call.type = SizedType(Type::inet, 24);
}
else if (call.func == "join") {
check_assignment(call, false, false);
check_nargs(call, 1);
......
......@@ -6,6 +6,7 @@
#include <sstream>
#include <sys/epoll.h>
#include <time.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
......@@ -345,6 +346,10 @@ std::vector<uint64_t> BPFtrace::get_arg_values(std::vector<Field> args, uint8_t*
resolve_usym(*(uint64_t*)(arg_data+arg.offset), *(uint64_t*)(arg_data+arg.offset + 8)).c_str()));
arg_values.push_back((uint64_t)resolved_symbols.back().get());
break;
case Type::inet:
name = strdup(resolve_inet(*(uint64_t*)(arg_data+arg.offset), *(uint64_t*)(arg_data+arg.offset+8)).c_str());
arg_values.push_back((uint64_t)name);
break;
case Type::username:
resolved_usernames.emplace_back(strdup(
resolve_uid(*(uint64_t*)(arg_data+arg.offset)).c_str()));
......@@ -747,6 +752,8 @@ int BPFtrace::print_map(IMap &map, uint32_t top, uint32_t div)
std::cout << resolve_sym(*(uintptr_t*)value.data());
else if (map.type_.type == Type::usym)
std::cout << resolve_usym(*(uintptr_t*)value.data(), *(uint64_t*)(value.data() + 8));
else if (map.type_.type == Type::inet)
std::cout << resolve_inet(*(uintptr_t*)value.data(), *(uint64_t*)(value.data() + 8));
else if (map.type_.type == Type::username)
std::cout << resolve_uid(*(uint64_t*)(value.data())) << std::endl;
else if (map.type_.type == Type::string)
......@@ -1337,6 +1344,21 @@ uint64_t BPFtrace::read_address_from_output(std::string output)
return std::stoull(first_word, 0, 16);
}
std::string BPFtrace::resolve_inet(int af, uint64_t inet)
{
// FIXME ipv6 is a 128 bit type as an array, how to pass as argument?
if(af != AF_INET)
{
std::cerr << "ntop() currently only supports AF_INET (IPv4); IPv6 will be supported in the future." << std::endl;
return std::string("");
}
char addr_cstr[INET_ADDRSTRLEN];
inet_ntop(af, &inet, addr_cstr, INET_ADDRSTRLEN);
std::string addrstr(addr_cstr);
return addrstr;
}
std::string BPFtrace::resolve_usym(uintptr_t addr, int pid, bool show_offset)
{
struct bcc_symbol sym;
......
......@@ -61,6 +61,7 @@ public:
std::string get_stack(uint64_t stackidpid, bool ustack, int indent=0);
std::string resolve_sym(uintptr_t addr, bool show_offset=false);
std::string resolve_usym(uintptr_t addr, int pid, bool show_offset=false);
std::string resolve_inet(int af, uint64_t inet);
std::string resolve_uid(uintptr_t addr);
uint64_t resolve_kname(const std::string &name);
uint64_t resolve_uname(const std::string &name, const std::string &path);
......
......@@ -79,6 +79,8 @@ std::string MapKey::argument_value(BPFtrace &bpftrace,
return bpftrace.resolve_sym(*(uint64_t*)data);
case Type::usym:
return bpftrace.resolve_usym(*(uint64_t*)data, *(uint64_t*)(arg_data + 8));
case Type::inet:
return bpftrace.resolve_inet(*(uint64_t*)data, *(uint64_t*)(arg_data + 8));
case Type::username:
return bpftrace.resolve_uid(*(uint64_t*)data);
case Type::probe:
......
......@@ -34,7 +34,8 @@ std::string verify_format_string(const std::string &fmt, std::vector<Field> args
{
Type arg_type = args.at(i).type.type;
if (arg_type == Type::sym || arg_type == Type::usym || arg_type == Type::probe ||
arg_type == Type::username || arg_type == Type::stack || arg_type == Type::ustack)
arg_type == Type::username || arg_type == Type::stack || arg_type == Type::ustack ||
arg_type == Type::inet)
arg_type = Type::string; // Symbols should be printed as strings
int offset = 1;
......
......@@ -26,7 +26,7 @@ bool SizedType::operator==(const SizedType &t) const
bool SizedType::IsArray() const
{
return type == Type::string || type == Type::usym || (type == Type::cast && !is_pointer);
return type == Type::string || type == Type::usym || type == Type::inet || (type == Type::cast && !is_pointer);
}
std::string typestr(Type t)
......@@ -48,6 +48,7 @@ std::string typestr(Type t)
case Type::string: return "string"; break;
case Type::sym: return "sym"; break;
case Type::usym: return "usym"; break;
case Type::inet: return "inet"; break;
case Type::cast: return "cast"; break;
case Type::probe: return "probe"; break;
default: abort();
......
......@@ -33,6 +33,7 @@ enum class Type
join,
probe,
username,
inet,
};
std::ostream &operator<<(std::ostream &os, Type type);
......
#include "common.h"
namespace bpftrace {
namespace test {
namespace codegen {
TEST(codegen, call_usym_key)
{
test("kprobe:f { @x[ntop(2, 0xFFFFFFFF)] = count()}",
R"EXPECTED(; Function Attrs: nounwind
declare i64 @llvm.bpf.pseudo(i64, i64) #0
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1
define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" {
entry:
%"@x_val" = alloca i64, align 8
%"@x_key" = alloca [24 x i8], align 8
%1 = getelementptr inbounds [24 x i8], [24 x i8]* %"@x_key", i64 0, i64 0
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1)
%inet.sroa.0.0..sroa_cast = bitcast [24 x i8]* %"@x_key" to i64*
store i64 2, i64* %inet.sroa.0.0..sroa_cast, align 8
%inet.sroa.4.0..sroa_idx = getelementptr inbounds [24 x i8], [24 x i8]* %"@x_key", i64 0, i64 8
%inet.sroa.4.0..sroa_cast = bitcast i8* %inet.sroa.4.0..sroa_idx to i64*
store i64 4294967295, i64* %inet.sroa.4.0..sroa_cast, align 8
%inet.sroa.5.0..sroa_idx = getelementptr inbounds [24 x i8], [24 x i8]* %"@x_key", i64 0, i64 16
%inet.sroa.5.0..sroa_cast = bitcast i8* %inet.sroa.5.0..sroa_idx to i64*
store i64 0, i64* %inet.sroa.5.0..sroa_cast, align 8
%pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%lookup_elem = call i8* inttoptr (i64 1 to i8* (i8*, i8*)*)(i64 %pseudo, [24 x i8]* nonnull %"@x_key")
%map_lookup_cond = icmp eq i8* %lookup_elem, null
br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success
lookup_success: ; preds = %entry
%2 = load i64, i8* %lookup_elem, align 8
%phitmp = add i64 %2, 1
br label %lookup_merge
lookup_merge: ; preds = %entry, %lookup_success
%lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ]
%3 = bitcast i64* %"@x_val" to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3)
store i64 %lookup_elem_val.0, i64* %"@x_val", align 8
%pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1)
%update_elem = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo1, [24 x i8]* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1)
call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3)
ret i64 0
}
; Function Attrs: argmemonly nounwind
declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1
attributes #0 = { nounwind }
attributes #1 = { argmemonly nounwind }
)EXPECTED");
}
} // namespace codegen
} // namespace test
} // namespace bpftrace
......@@ -89,6 +89,7 @@ TEST(semantic_analyser, builtin_functions)
test("kprobe:f { join(0) }", 0);
test("kprobe:f { sym(0xffff) }", 0);
test("kprobe:f { usym(0xffff) }", 0);
test("kprobe:f { ntop(2, 0xffff) }", 0);
test("kprobe:f { reg(\"ip\") }", 0);
test("kprobe:f { @x = count(pid) }", 1);
test("kprobe:f { @x = sum(pid, 123) }", 1);
......@@ -244,6 +245,15 @@ TEST(semantic_analyser, call_usym)
test("kprobe:f { usym(\"hello\"); }", 10);
}
TEST(semantic_analyser, call_ntop)
{
test("kprobe:f { ntop(2, arg0); }", 0);
test("kprobe:f { @x = ntop(2, arg0); }", 0);
test("kprobe:f { @x = ntop(2, 0xFFFF); }", 0);
test("kprobe:f { ntop(); }", 1);
test("kprobe:f { ntop(2, \"hello\"); }", 10);
}
TEST(semantic_analyser, call_kaddr)
{
test("kprobe:f { kaddr(\"avenrun\"); }", 0);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment