Mini Shell
# Copyright 2019 Clevernet
#
# 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.
from os import linesep
import ctypes as ct
from .table import get_table_type_name
class OffsetUnion(ct.Union):
_fields_ = [('offsetu', ct.c_uint16), ('offset', ct.c_int16)]
class ImmUnion(ct.Union):
_fields_ = [('immu', ct.c_uint32), ('imm', ct.c_int32)]
class BPFInstrFields(ct.Structure):
_pack_ = 1
_anonymous_ = ('o', 'i')
_fields_ = [('opcode', ct.c_uint8),
('dst', ct.c_uint8, 4),
('src', ct.c_uint8, 4),
('o', OffsetUnion),
('i', ImmUnion)]
class BPFInstr(ct.Union):
_pack_ = 1
_anonymous_ = ('s')
_fields_ = [('s', BPFInstrFields), ('instr', ct.c_uint64)]
class BPFDecoder():
BPF_PSEUDO_CALL = 1
bpf_helpers = ['unspec',
'map_lookup_elem',
'map_update_elem',
'map_delete_elem',
'probe_read',
'ktime_get_ns',
'trace_printk',
'get_prandom_u32',
'get_smp_processor_id',
'skb_store_bytes',
'l3_csum_replace',
'l4_csum_replace',
'tail_call',
'clone_redirect',
'get_current_pid_tgid',
'get_current_uid_gid',
'get_current_comm',
'get_cgroup_classid',
'skb_vlan_push',
'skb_vlan_pop',
'skb_get_tunnel_key',
'skb_set_tunnel_key',
'perf_event_read',
'redirect',
'get_route_realm',
'perf_event_output',
'skb_load_bytes',
'get_stackid',
'csum_diff',
'skb_get_tunnel_opt',
'skb_set_tunnel_opt',
'skb_change_proto',
'skb_change_type',
'skb_under_cgroup',
'get_hash_recalc',
'get_current_task',
'probe_write_user',
'current_task_under_cgroup',
'skb_change_tail',
'skb_pull_data',
'csum_update',
'set_hash_invalid',
'get_numa_node_id',
'skb_change_head',
'xdp_adjust_head',
'probe_read_str',
'get_socket_cookie',
'get_socket_uid',
'set_hash',
'setsockopt',
'skb_adjust_room',
'redirect_map',
'sk_redirect_map',
'sock_map_update',
'xdp_adjust_meta',
'perf_event_read_value',
'perf_prog_read_value',
'getsockopt',
'override_return',
'sock_ops_cb_flags_set',
'msg_redirect_map',
'msg_apply_bytes',
'msg_cork_bytes',
'msg_pull_data',
'bind',
'xdp_adjust_tail',
'skb_get_xfrm_state',
'get_stack',
'skb_load_bytes_relative',
'fib_lookup',
'sock_hash_update',
'msg_redirect_hash',
'sk_redirect_hash',
'lwt_push_encap',
'lwt_seg6_store_bytes',
'lwt_seg6_adjust_srh',
'lwt_seg6_action',
'rc_repeat',
'rc_keydown',
'skb_cgroup_id',
'get_current_cgroup_id',
'get_local_storage',
'sk_select_reuseport',
'skb_ancestor_cgroup_id',
'sk_lookup_tcp',
'sk_lookup_udp',
'sk_release',
'map_push_elem',
'map_pop_elem',
'map_peek_elem',
'msg_push_data',
'msg_pop_data',
'rc_pointer_rel']
opcodes = {0x04: ('add32', 'dstimm', '+=', 32),
0x05: ('ja', 'joff', None, 64),
0x07: ('add', 'dstimm', '+=', 64),
0x0c: ('add32', 'dstsrc', '+=', 32),
0x0f: ('add', 'dstsrc', '+=', 64),
0x14: ('sub32', 'dstimm', '-=', 32),
0x15: ('jeq', 'jdstimmoff', '==', 64),
0x17: ('sub', 'dstimm', '-=', 64),
0x18: ('lddw', 'lddw', None, 64),
0x1c: ('sub32', 'dstsrc', '-=', 32),
0x1d: ('jeq', 'jdstsrcoff', '==', 64),
0x1f: ('sub', 'dstsrc', '-=', 64),
0x20: ('ldabsw', 'ldabs', None, 32),
0x24: ('mul32', 'dstimm', '*=', 32),
0x25: ('jgt', 'jdstimmoff', '>', 64),
0x27: ('mul', 'dstimm', '*=', 64),
0x28: ('ldabsh', 'ldabs', None, 16),
0x2c: ('mul32', 'dstsrc', '*=', 32),
0x2d: ('jgt', 'jdstsrcoff', '>', 64),
0x2f: ('mul', 'dstsrc', '*=', 64),
0x30: ('ldabsb', 'ldabs', None, 8),
0x34: ('div32', 'dstimm', '/=', 32),
0x35: ('jge', 'jdstimmoff', '>=', 64),
0x37: ('div', 'dstimm', '/=', 64),
0x38: ('ldabsdw', 'ldabs', None, 64),
0x3c: ('div32', 'dstsrc', '/=', 32),
0x3d: ('jge', 'jdstsrcoff', '>=', 64),
0x3f: ('div', 'dstsrc', '/=', 64),
0x40: ('ldindw', 'ldind', None, 32),
0x44: ('or32', 'dstimm_bw', '|=', 32),
0x45: ('jset', 'jdstimmoff', '&', 64),
0x47: ('or', 'dstimm_bw', '|=', 64),
0x48: ('ldindh', 'ldind', None, 16),
0x4c: ('or32', 'dstsrc', '|=', 32),
0x4d: ('jset', 'jdstsrcoff', '&', 64),
0x4f: ('or', 'dstsrc', '|=', 64),
0x50: ('ldindb', 'ldind', None, 8),
0x54: ('and32', 'dstimm_bw', '&=', 32),
0x55: ('jne', 'jdstimmoff', '!=', 64),
0x57: ('and', 'dstimm_bw', '&=', 64),
0x58: ('ldinddw', 'ldind', None, 64),
0x5c: ('and32', 'dstsrc', '&=', 32),
0x5d: ('jne', 'jdstsrcoff', '!=', 64),
0x5f: ('and', 'dstsrc', '&=', 64),
0x61: ('ldxw', 'ldstsrcoff', None, 32),
0x62: ('stw', 'sdstoffimm', None, 32),
0x63: ('stxw', 'sdstoffsrc', None, 32),
0x64: ('lsh32', 'dstimm', '<<=', 32),
0x65: ('jsgt', 'jdstimmoff', 's>', 64),
0x67: ('lsh', 'dstimm', '<<=', 64),
0x69: ('ldxh', 'ldstsrcoff', None, 16),
0x6a: ('sth', 'sdstoffimm', None, 16),
0x6b: ('stxh', 'sdstoffsrc', None, 16),
0x6c: ('lsh32', 'dstsrc', '<<=', 32),
0x6d: ('jsgt', 'jdstsrcoff', 's>', 64),
0x6f: ('lsh', 'dstsrc', '<<=', 64),
0x71: ('ldxb', 'ldstsrcoff', None, 8),
0x72: ('stb', 'sdstoffimm', None, 8),
0x73: ('stxb', 'sdstoffsrc', None, 8),
0x74: ('rsh32', 'dstimm', '>>=', 32),
0x75: ('jsge', 'jdstimmoff', 's>=', 64),
0x77: ('rsh', 'dstimm', '>>=', 64),
0x79: ('ldxdw', 'ldstsrcoff', None, 64),
0x7a: ('stdw', 'sdstoffimm', None, 64),
0x7b: ('stxdw', 'sdstoffsrc', None, 64),
0x7c: ('rsh32', 'dstsrc', '>>=', 32),
0x7d: ('jsge', 'jdstsrcoff', 's>=', 64),
0x7f: ('rsh', 'dstsrc', '>>=', 64),
0x84: ('neg32', 'dst', '~', 32),
0x85: ('call', 'call', None, 64),
0x87: ('neg', 'dst', '~', 64),
0x94: ('mod32', 'dstimm', '%=', 32),
0x95: ('exit', 'exit', None, 64),
0x97: ('mod', 'dstimm', '%=', 64),
0x9c: ('mod32', 'dstsrc', '%=', 32),
0x9f: ('mod', 'dstsrc', '%=', 64),
0xa4: ('xor32', 'dstimm_bw', '^=', 32),
0xa5: ('jlt', 'jdstimmoff', '<', 64),
0xa7: ('xor', 'dstimm_bw', '^=', 64),
0xac: ('xor32', 'dstsrc', '^=', 32),
0xad: ('jlt', 'jdstsrcoff', '<', 64),
0xaf: ('xor', 'dstsrc', '^=', 64),
0xb4: ('mov32', 'dstimm', '=', 32),
0xb5: ('jle', 'jdstimmoff', '<=', 64),
0xb7: ('mov', 'dstimm', '=', 64),
0xbc: ('mov32', 'dstsrc', '=', 32),
0xbd: ('jle', 'jdstsrcoff', '<=', 64),
0xbf: ('mov', 'dstsrc', '=', 64),
0xc4: ('arsh32', 'dstimm', 's>>=', 32),
0xc5: ('jslt', 'jdstimmoff', 's<', 64),
0xc7: ('arsh', 'dstimm', 's>>=', 64),
0xcc: ('arsh32', 'dstsrc', 's>>=', 32),
0xcd: ('jslt', 'jdstsrcoff', 's<', 64),
0xcf: ('arsh', 'dstsrc', 's>>=', 64),
0xd5: ('jsle', 'jdstimmoff', 's<=', 64),
0xdc: ('endian32', 'dstsrc', 'endian', 32),
0xdd: ('jsle', 'jdstimmoff', 's<=', 64),}
@classmethod
def decode(cls, i, w, w1):
try:
name, opclass, op, bits = cls.opcodes[w.opcode]
if opclass == 'dstimm':
return 'r%d %s %d' % (w.dst, op, w.imm), 0
elif opclass == 'dstimm_bw':
return 'r%d %s 0x%x' % (w.dst, op, w.immu), 0
elif opclass == 'joff':
return 'goto %s <%d>' % ('%+d' % (w.offset),
i + w.offset + 1), 0
elif opclass == 'dstsrc':
return 'r%d %s r%d' % (w.dst, op, w.src), 0
elif opclass == 'jdstimmoff':
return 'if r%d %s %d goto pc%s <%d>' % (w.dst, op, w.imm,
'%+d' % (w.offset),
i + w.offset + 1), 0
elif opclass == 'jdstsrcoff':
return 'if r%d %s r%d goto pc%s <%d>' % (w.dst, op, w.src,
'%+d' % (w.offset),
i + w.offset + 1), 0
elif opclass == 'lddw':
# imm contains the file descriptor (FD) of the map being loaded;
# the kernel will translate this into the proper address
if w1 is None:
raise Exception("lddw requires two instructions to be disassembled")
if w1.imm == 0:
return 'r%d = <map at fd #%d>' % (w.dst, w.imm), 1
imm = (w1.imm << 32) | w.imm
return 'r%d = 0x%x' % (w.dst, imm), 1
elif opclass == 'ldabs':
return 'r0 = *(u%s*)skb[%s]' % (bits, w.imm), 0
elif opclass == 'ldind':
return 'r0 = *(u%d*)skb[r%d %s]' % (bits, w.src,
'%+d' % (w.imm)), 0
elif opclass == 'ldstsrcoff':
return 'r%d = *(u%d*)(r%d %s)' % (w.dst, bits, w.src,
'%+d' % (w.offset)), 0
elif opclass == 'sdstoffimm':
return '*(u%d*)(r%d %s) = %d' % (bits, w.dst,
'%+d' % (w.offset), w.imm), 0
elif opclass == 'sdstoffsrc':
return '*(u%d*)(r%d %s) = r%d' % (bits, w.dst,
'%+d' % (w.offset), w.src), 0
elif opclass == 'dst':
return 'r%d = %s (u%s)r%d' % (w.dst, op, bits, w.dst), 0
elif opclass == 'call':
if w.src != cls.BPF_PSEUDO_CALL:
try:
return '%s bpf_%s#%d' % (name, cls.bpf_helpers[w.immu], w.immu), 0
except IndexError:
return '%s <unknown helper #%d>' % (op, w.immu), 0
return '%s %s' % (name, '%+d' % (w.imm)), 0
elif opclass == 'exit':
return name, 0
else:
raise Exception('unknown opcode class')
except KeyError:
return 'unknown <0x%x>' % (w.opcode)
def disassemble_instruction(i, w0, w1=None):
instr, skip = BPFDecoder.decode(i, w0, w1)
return "%4d: (%02x) %s" % (i, w0.opcode, instr), skip
def disassemble_str(bpfstr):
ptr = ct.cast(ct.c_char_p(bpfstr), ct.POINTER(BPFInstr))
numinstr = int(len(bpfstr) / 8)
w0 = ptr[0]
skip = 0
instr_list = []
for i in range(1, numinstr):
w1 = ptr[i]
if skip:
skip -= 1
instr_str = "%4d: (64-bit upper word)" % (i)
else:
instr_str, skip = disassemble_instruction(i - 1, w0, w1)
instr_list.append(instr_str)
w0 = w1
instr_str, skip = disassemble_instruction(numinstr - 1, w0, None)
instr_list.append(instr_str)
return instr_list
def disassemble_prog(func_name, bpfstr):
instr_list = ["Disassemble of BPF program %s:" % (func_name)]
instr_list += disassemble_str(bpfstr)
return linesep.join(instr_list)
class MapDecoder ():
ctype2str = {ct.c_bool: u"_Bool",
ct.c_char: u"char",
ct.c_wchar: u"wchar_t",
ct.c_ubyte: u"unsigned char",
ct.c_short: u"short",
ct.c_ushort: u"unsigned short",
ct.c_int: u"int",
ct.c_uint: u"unsigned int",
ct.c_long: u"long",
ct.c_ulong: u"unsigned long",
ct.c_longlong: u"long long",
ct.c_ulonglong: u"unsigned long long",
ct.c_float: u"float",
ct.c_double: u"double",
ct.c_longdouble: u"long double",
ct.c_int64 * 2: u"__int128",
ct.c_uint64 * 2: u"unsigned __int128",}
@classmethod
def get_ct_name(cls, t):
try:
if issubclass(t, ct.Structure):
field_type_name = "struct"
elif issubclass(t, ct.Union):
field_type_name = "union"
elif issubclass(t, ct.Array):
field_type_name = cls.ctype2str[t._type_] + "[" + str(t._length_) + "]"
else:
field_type_name = cls.ctype2str[t]
except KeyError:
field_type_name = str(t)
return field_type_name
@classmethod
def format_size_info(cls, offset, size, enabled=False, bitoffset=None):
if not enabled:
return ""
if bitoffset is not None:
return "[%d,%d +%d bit]" % (offset, bitoffset, size)
return "[%d +%d] " % (offset, size)
@classmethod
def print_ct_map(cls, t, indent="", offset=0, sizeinfo=False):
map_lines = []
try:
for field_name, field_type in t._fields_:
is_structured = (issubclass(field_type, ct.Structure) or
issubclass(field_type, ct.Union))
field_type_name = cls.get_ct_name(field_type)
field_offset = getattr(t, field_name).offset
field_size = ct.sizeof(field_type)
sizedesc = cls.format_size_info(offset + field_offset,
field_size, sizeinfo)
if is_structured:
map_lines.append("%s%s%s {" % (indent, sizedesc, field_type_name))
map_lines += cls.print_ct_map(field_type,
indent + " ",
offset + field_offset)
map_lines.append("%s} %s;" % (indent, field_name))
else:
map_lines.append("%s%s%s %s;" % (indent, sizedesc,
field_type_name,
field_name))
except ValueError:
# is a bit field
offset_bits = 0
for field in t._fields_:
if len(field) == 3:
field_name, field_type, field_bits = field
field_type_name = cls.get_ct_name(field_type)
sizedesc = cls.format_size_info(offset, offset_bits,
sizeinfo, field_bits)
map_lines.append("%s%s%s %s:%d;" % (indent, sizedesc,
field_type_name,
field_name,
field_bits))
else:
# end of previous bit field
field_name, field_type = field
field_type_name = cls.get_ct_name(field_type)
field_offset = getattr(t, field_name).offset
field_size = ct.sizeof(field_type)
field_bits = 0
offset_bits = 0
sizedesc = cls.format_size_info(offset + field_offset,
field_size, sizeinfo)
map_lines.append("%s%s%s %s;" % (indent, sizedesc,
field_type_name,
field_name))
offset += field_offset
offset_bits += field_bits
return map_lines
@classmethod
def print_map_ctype(cls, t, field_name, sizeinfo):
is_structured = (issubclass(t, ct.Structure) or
issubclass(t, ct.Union))
type_name = cls.get_ct_name(t)
if is_structured:
map_lines = [" %s {" % (type_name)]
map_lines += cls.print_ct_map(t, " ", sizeinfo=sizeinfo)
map_lines.append(" } %s;" % (field_name))
else:
map_lines = [" %s %s;" % (type_name, field_name)]
return map_lines
@classmethod
def decode_map(cls, map_name, map_obj, map_type, sizeinfo=False):
map_lines = ['Layout of BPF map %s (type %s, FD %d, ID %d):' % (map_name,
map_type,
map_obj.map_fd,
map_obj.map_id)]
map_lines += cls.print_map_ctype(map_obj.Key, 'key', sizeinfo=sizeinfo)
map_lines += cls.print_map_ctype(map_obj.Leaf, 'value', sizeinfo=sizeinfo)
return linesep.join(map_lines)
def decode_map(map_name, map_obj, map_type, sizeinfo=False):
map_type_name = get_table_type_name(map_type)
return MapDecoder.decode_map(map_name, map_obj, map_type_name, sizeinfo=sizeinfo)
Zerion Mini Shell 1.0