diff --git a/cpanfile b/cpanfile index f8fb7e8..4a619a6 100644 --- a/cpanfile +++ b/cpanfile @@ -3,4 +3,5 @@ requires 'perl', '5.008001'; on 'test' => sub { requires 'Test::More', '0.98'; requires 'Minilla'; + requires 'IO::Interface::Simple'; }; diff --git a/lib/Sys/Ebpf/Link/Netlink/Constants/Iflink.pm b/lib/Sys/Ebpf/Link/Netlink/Constants/Iflink.pm new file mode 100644 index 0000000..7ea79fd --- /dev/null +++ b/lib/Sys/Ebpf/Link/Netlink/Constants/Iflink.pm @@ -0,0 +1,50 @@ +package Sys::Ebpf::Link::Netlink::Constants::Iflink; + +use strict; +use warnings; +use utf8; + +use Exporter 'import'; + +# cf. https://github.com/torvalds/linux/blob/master/include/uapi/linux/if_link.h +my %constants = ( + + # IFLA_AF_SPEC section + 'IFLA_UNSPEC' => 0, + 'IFLA_XDP' => 43, + + # /* XDP section */ + 'XDP_FLAGS_UPDATE_IF_NOEXIST' => 1 << 0, + 'XDP_FLAGS_SKB_MODE' => 1 << 1, + 'XDP_FLAGS_DRV_MODE' => 1 << 2, + 'XDP_FLAGS_HW_MODE' => 1 << 3, + 'XDP_FLAGS_REPLACE' => 1 << 4, + 'XDP_FLAGS_MODES' => ( 1 << 1 | 1 << 2 | 1 << 3 ), + 'XDP_FLAGS_MASK' => ( 1 << 0 | 1 << 1 | 1 << 2 | 1 << 3 | 1 << 4 ), + 'XDP_ATTACHED_NONE' => 0, + 'XDP_ATTACHED_DRV' => 1, + 'XDP_ATTACHED_SKB' => 2, + 'XDP_ATTACHED_HW' => 3, + 'XDP_ATTACHED_MULTI' => 4, + 'IFLA_XDP_UNSPEC' => 0, + 'IFLA_XDP_FD' => 1, + 'IFLA_XDP_ATTACHED' => 2, + 'IFLA_XDP_FLAGS' => 3, + 'IFLA_XDP_PROG_ID' => 4, + 'IFLA_XDP_DRV_PROG_ID' => 5, + 'IFLA_XDP_SKB_PROG_ID' => 6, + 'IFLA_XDP_HW_PROG_ID' => 7, + 'IFLA_XDP_EXPECTED_FD' => 8, +); + +# Export all constants +our @EXPORT_OK = keys %constants; +our %EXPORT_TAGS = ( all => \@EXPORT_OK ); + +# Define constants as subroutines +for my $name (@EXPORT_OK) { + no strict 'refs'; + *{$name} = sub () { $constants{$name} }; +} + +1; diff --git a/lib/Sys/Ebpf/Link/Netlink/Constants/Netlink.pm b/lib/Sys/Ebpf/Link/Netlink/Constants/Netlink.pm new file mode 100644 index 0000000..ca2b7d6 --- /dev/null +++ b/lib/Sys/Ebpf/Link/Netlink/Constants/Netlink.pm @@ -0,0 +1,97 @@ +package Sys::Ebpf::Link::Netlink::Constants::Netlink; + +use strict; +use warnings; +use utf8; + +use Exporter 'import'; + +# cf. https://github.com/torvalds/linux/blob/master/tools/include/uapi/linux/netlink.h +my %constants = ( + 'NETLINK_ROUTE' => 0, + 'NETLINK_UNUSED' => 1, + 'NETLINK_USERSOCK' => 2, + 'NETLINK_FIREWALL' => 3, + 'NETLINK_SOCK_DIAG' => 4, + 'NETLINK_NFLOG' => 5, + 'NETLINK_XFRM' => 6, + 'NETLINK_SELINUX' => 7, + 'NETLINK_ISCSI' => 8, + 'NETLINK_AUDIT' => 9, + 'NETLINK_FIB_LOOKUP' => 10, + 'NETLINK_CONNECTOR' => 11, + 'NETLINK_NETFILTER' => 12, + 'NETLINK_IP6_FW' => 13, + 'NETLINK_DNRTMSG' => 14, + 'NETLINK_KOBJECT_UEVENT' => 15, + 'NETLINK_GENERIC' => 16, + 'NETLINK_SCSITRANSPORT' => 18, + 'NETLINK_ECRYPTFS' => 19, + 'NETLINK_RDMA' => 20, + 'NETLINK_CRYPTO' => 21, + 'NETLINK_SMC' => 22, + + 'NLMSG_NOOP' => 1, + 'NLMSG_ERROR' => 2, + 'NLMSG_DONE' => 3, + 'NLMSG_OVERRUN' => 4, + + # /* Flags values */ + 'NLM_F_REQUEST' => 0x01, + 'NLM_F_MULTI' => 0x02, + 'NLM_F_ACK' => 0x04, + 'NLM_F_ECHO' => 0x08, + 'NLM_F_DUMP_INTR' => 0x10, + 'NLM_F_DUMP_FILTERED' => 0x20, + + # /* Modifiers to GET request */ + 'NLM_F_ROOT' => 0x100, + 'NLM_F_MATCH' => 0x200, + 'NLM_F_ATOMIC' => 0x400, + + # 'NLM_F_DUMP' => ( NLM_F_ROOT | NLM_F_MATCH ), + + # /* Modifiers to NEW request */ + 'NLM_F_REPLACE' => 0x100, + 'NLM_F_EXCL' => 0x200, + 'NLM_F_CREATE' => 0x400, + 'NLM_F_APPEND' => 0x800, + + # /* Modifiers to DELETE request */ + 'NLM_F_NONREC' => 0x100, + + # /* Flags for ACK message */ + 'NLM_F_CAPPED' => 0x100, + 'NLM_F_ACK_TLVS' => 0x200, + + # /* Attribute types */ + 'NLA_F_NESTED' => ( 1 << 15 ), + 'NLA_F_NET_BYTEORDER' => ( 1 << 14 ), + 'NLMSG_HDRLEN' => 16, + 'NLA_HDRLEN' => 4, + + # Socket options + 'NETLINK_ADD_MEMBERSHIP' => 1, + 'NETLINK_DROP_MEMBERSHIP' => 2, + 'NETLINK_PKTINFO' => 3, + 'NETLINK_BROADCAST_ERROR' => 4, + 'NETLINK_NO_ENOBUFS' => 5, + 'NETLINK_RX_RING' => 6, + 'NETLINK_TX_RING' => 7, + 'NETLINK_LISTEN_ALL_NSID' => 8, + 'NETLINK_CAP_ACK' => 10, + 'NETLINK_EXT_ACK' => 11, + 'NETLINK_GET_STRICT_CHK' => 12, +); + +# Export all constants +our @EXPORT_OK = keys %constants; +our %EXPORT_TAGS = ( all => \@EXPORT_OK ); + +# Define constants as subroutines +for my $name (@EXPORT_OK) { + no strict 'refs'; + *{$name} = sub () { $constants{$name} }; +} + +1; diff --git a/lib/Sys/Ebpf/Link/Netlink/Constants/Rtnetlink.pm b/lib/Sys/Ebpf/Link/Netlink/Constants/Rtnetlink.pm new file mode 100644 index 0000000..057b279 --- /dev/null +++ b/lib/Sys/Ebpf/Link/Netlink/Constants/Rtnetlink.pm @@ -0,0 +1,96 @@ +package Sys::Ebpf::Link::Netlink::Constants::Rtnetlink; + +use strict; +use warnings; +use utf8; + +use Exporter 'import'; + +# cf. https://github.com/torvalds/linux/blob/master/include/uapi/linux/rtnetlink.h +my %constants = ( + + # /* Routing table identifiers. */ + # /* Types of messages */ + 'RTM_BASE' => 16, + 'RTM_NEWLINK' => 16, + 'RTM_DELLINK' => 17, + 'RTM_GETLINK' => 18, + 'RTM_SETLINK' => 19, + 'RTM_NEWADDR' => 20, + 'RTM_DELADDR' => 21, + 'RTM_GETADDR' => 22, + 'RTM_NEWROUTE' => 24, + 'RTM_DELROUTE' => 25, + 'RTM_GETROUTE' => 26, + 'RTM_NEWNEIGH' => 28, + 'RTM_DELNEIGH' => 29, + 'RTM_GETNEIGH' => 30, + 'RTM_NEWRULE' => 32, + 'RTM_DELRULE' => 33, + 'RTM_GETRULE' => 34, + 'RTM_NEWQDISC' => 36, + 'RTM_DELQDISC' => 37, + 'RTM_GETQDISC' => 38, + 'RTM_NEWTCLASS' => 40, + 'RTM_DELTCLASS' => 41, + 'RTM_GETTCLASS' => 42, + 'RTM_NEWTFILTER' => 44, + 'RTM_DELTFILTER' => 45, + 'RTM_GETTFILTER' => 46, + 'RTM_NEWACTION' => 48, + 'RTM_DELACTION' => 49, + 'RTM_GETACTION' => 50, + 'RTM_NEWPREFIX' => 52, + 'RTM_GETMULTICAST' => 58, + 'RTM_GETANYCAST' => 62, + 'RTM_NEWNEIGHTBL' => 64, + 'RTM_GETNEIGHTBL' => 66, + 'RTM_SETNEIGHTBL' => 67, + 'RTM_NEWNDUSEROPT' => 68, + 'RTM_NEWADDRLABEL' => 72, + 'RTM_DELADDRLABEL' => 73, + 'RTM_GETADDRLABEL' => 74, + 'RTM_GETDCB' => 78, + 'RTM_SETDCB' => 79, + 'RTM_NEWNETCONF' => 80, + 'RTM_GETNETCONF' => 82, + 'RTM_NEWMDB' => 84, + 'RTM_DELMDB' => 85, + 'RTM_GETMDB' => 86, + 'RTM_NEWNSID' => 88, + 'RTM_DELNSID' => 89, + 'RTM_GETNSID' => 90, + 'RTM_NEWSTATS' => 92, + 'RTM_GETSTATS' => 94, + 'RTM_NEWCACHEREPORT' => 96, + 'RTM_NEWCHAIN' => 100, + 'RTM_DELCHAIN' => 101, + 'RTM_GETCHAIN' => 102, + 'RTM_NEWNEXTHOP' => 104, + 'RTM_DELNEXTHOP' => 105, + 'RTM_GETNEXTHOP' => 106, + 'RTM_NEWLINKPROP' => 108, + 'RTM_DELLINKPROP' => 109, + 'RTM_GETLINKPROP' => 110, + 'RTM_NEWVLAN' => 112, + 'RTM_DELVLAN' => 113, + 'RTM_GETVLAN' => 114, + 'RTM_NEWNEXTHOPBUCKET' => 116, + 'RTM_DELNEXTHOPBUCKET' => 117, + 'RTM_GETNEXTHOPBUCKET' => 118, + 'RTM_NEWTUNNEL' => 120, + 'RTM_DELTUNNEL' => 121, + 'RTM_GETTUNNEL' => 122, +); + +# Export all constants +our @EXPORT_OK = keys %constants; +our %EXPORT_TAGS = ( all => \@EXPORT_OK ); + +# Define constants as subroutines +for my $name (@EXPORT_OK) { + no strict 'refs'; + *{$name} = sub () { $constants{$name} }; +} + +1; diff --git a/lib/Sys/Ebpf/Link/Netlink/Constants/Socket.pm b/lib/Sys/Ebpf/Link/Netlink/Constants/Socket.pm new file mode 100644 index 0000000..c4346b4 --- /dev/null +++ b/lib/Sys/Ebpf/Link/Netlink/Constants/Socket.pm @@ -0,0 +1,165 @@ +package Sys::Ebpf::Link::Netlink::Constants::Socket; + +use strict; +use warnings; +use utf8; + +use Exporter 'import'; + +# cf. https://github.com/torvalds/linux/blob/master/include/linux/socket.h +my %constants = ( + + # /* Supported address families. */ + 'AF_UNSPEC' => 0, + 'AF_UNIX' => 1, + 'AF_INET' => 2, + 'AF_AX25' => 3, + 'AF_IPX' => 4, + 'AF_APPLETALK' => 5, + 'AF_NETROM' => 6, + 'AF_BRIDGE' => 7, + 'AF_ATMPVC' => 8, + 'AF_X25' => 9, + 'AF_INET6' => 10, + 'AF_ROSE' => 11, + 'AF_DECnet' => 12, + 'AF_NETBEUI' => 13, + 'AF_SECURITY' => 14, + 'AF_KEY' => 15, + 'AF_NETLINK' => 16, + 'AF_ROUTE' => 16, + 'AF_PACKET' => 17, + 'AF_ASH' => 18, + 'AF_ECONET' => 19, + 'AF_ATMSVC' => 20, + 'AF_RDS' => 21, + 'AF_SNA' => 22, + 'AF_IRDA' => 23, + 'AF_PPPOX' => 24, + 'AF_WANPIPE' => 25, + 'AF_LLC' => 26, + 'AF_IB' => 27, + 'AF_MPLS' => 28, + 'AF_CAN' => 29, + 'AF_TIPC' => 30, + 'AF_BLUETOOTH' => 31, + 'AF_IUCV' => 32, + 'AF_RXRPC' => 33, + 'AF_ISDN' => 34, + 'AF_PHONET' => 35, + 'AF_IEEE802154' => 36, + 'AF_CAIF' => 37, + 'AF_ALG' => 38, + 'AF_NFC' => 39, + 'AF_VSOCK' => 40, + 'AF_KCM' => 41, + 'AF_QIPCRTR' => 42, + 'AF_SMC' => 43, + 'AF_XDP' => 44, + 'AF_MCTP' => 45, + + # /* Protocol families, same as address families. */ + 'PF_UNSPEC' => 0, + 'PF_UNIX' => 1, + 'PF_LOCAL' => 1, + 'PF_INET' => 2, + 'PF_AX25' => 3, + 'PF_IPX' => 4, + 'PF_APPLETALK' => 5, + 'PF_NETROM' => 6, + 'PF_BRIDGE' => 7, + 'PF_ATMPVC' => 8, + 'PF_X25' => 9, + 'PF_INET6' => 10, + 'PF_ROSE' => 11, + 'PF_DECnet' => 12, + 'PF_NETBEUI' => 13, + 'PF_SECURITY' => 14, + 'PF_KEY' => 15, + 'PF_NETLINK' => 16, + 'PF_ROUTE' => 16, + 'PF_PACKET' => 17, + 'PF_ASH' => 18, + 'PF_ECONET' => 19, + 'PF_ATMSVC' => 20, + 'PF_RDS' => 21, + 'PF_SNA' => 22, + 'PF_IRDA' => 23, + 'PF_PPPOX' => 24, + 'PF_WANPIPE' => 25, + 'PF_LLC' => 26, + 'PF_IB' => 27, + 'PF_MPLS' => 28, + 'PF_CAN' => 29, + 'PF_TIPC' => 30, + 'PF_BLUETOOTH' => 31, + 'PF_IUCV' => 32, + 'PF_RXRPC' => 33, + 'PF_ISDN' => 34, + 'PF_PHONET' => 35, + 'PF_IEEE802154' => 36, + 'PF_CAIF' => 37, + 'PF_ALG' => 38, + 'PF_NFC' => 39, + 'PF_VSOCK' => 40, + 'PF_KCM' => 41, + 'PF_QIPCRTR' => 42, + 'PF_SMC' => 43, + 'PF_XDP' => 44, + 'PF_MCTP' => 45, + + # /* Setsockoptions(2) level. Thanks to BSD these must match IPPROTO_xxx */ + 'SOL_IP' => 0, + 'SOL_SOCKET' => 1, + 'SOL_TCP' => 6, + 'SOL_UDP' => 17, + 'SOL_IPV6' => 41, + 'SOL_ICMPV6' => 58, + 'SOL_SCTP' => 132, + 'SOL_UDPLITE' => 136, + 'SOL_RAW' => 255, + 'SOL_IPX' => 256, + 'SOL_AX25' => 257, + 'SOL_ATALK' => 258, + 'SOL_NETROM' => 259, + 'SOL_ROSE' => 260, + 'SOL_DECNET' => 261, + 'SOL_X25' => 262, + 'SOL_PACKET' => 263, + 'SOL_ATM' => 264, + 'SOL_AAL' => 265, + 'SOL_IRDA' => 266, + 'SOL_NETBEUI' => 267, + 'SOL_LLC' => 268, + 'SOL_DCCP' => 269, + 'SOL_NETLINK' => 270, + 'SOL_TIPC' => 271, + 'SOL_RXRPC' => 272, + 'SOL_PPPOL2TP' => 273, + 'SOL_BLUETOOTH' => 274, + 'SOL_PNPIPE' => 275, + 'SOL_RDS' => 276, + 'SOL_IUCV' => 277, + 'SOL_CAIF' => 278, + 'SOL_ALG' => 279, + 'SOL_NFC' => 280, + 'SOL_KCM' => 281, + 'SOL_TLS' => 282, + 'SOL_XDP' => 283, + 'SOL_MPTCP' => 284, + 'SOL_MCTP' => 285, + 'SOL_SMC' => 286, + 'SOL_VSOCK' => 287, +); + +# Export all constants +our @EXPORT_OK = keys %constants; +our %EXPORT_TAGS = ( all => \@EXPORT_OK ); + +# Define constants as subroutines +for my $name (@EXPORT_OK) { + no strict 'refs'; + *{$name} = sub () { $constants{$name} }; +} + +1; diff --git a/lib/Sys/Ebpf/Link/Netlink/Socket.pm b/lib/Sys/Ebpf/Link/Netlink/Socket.pm new file mode 100644 index 0000000..2d51568 --- /dev/null +++ b/lib/Sys/Ebpf/Link/Netlink/Socket.pm @@ -0,0 +1,178 @@ +package Sys::Ebpf::Link::Netlink::Socket; + +use strict; +use warnings; +use Socket qw( SOCK_RAW ); +use Errno (); +use Exporter 'import'; + +use Sys::Ebpf::Link::Netlink::Constants::Netlink qw( + NETLINK_ROUTE + NLMSG_HDRLEN + NLA_HDRLEN + NLM_F_REQUEST + NLM_F_ACK + NETLINK_EXT_ACK +); +use Sys::Ebpf::Link::Netlink::Constants::Socket qw( + PF_NETLINK + AF_NETLINK + SOL_NETLINK +); + +our @EXPORT_OK = qw( + pack_sockaddr_nl + pack_nlattr + pack_nlmsghdr + pack_ifinfomsg +); + +sub new { + my ( $class, %args ) = @_; + my $self = {}; + bless $self, $class; + + # Netlinkソケットを作成 + socket( $self->{sock}, PF_NETLINK, SOCK_RAW, + $args{Proto} || NETLINK_ROUTE ) + or die "Failed to create Netlink socket: $!"; + + # 拡張エラーメッセージを有効にする + my $one = pack( 'i', 1 ); + setsockopt( $self->{sock}, SOL_NETLINK, NETLINK_EXT_ACK, $one ) + or warn "Failed to set NETLINK_EXT_ACK: $!"; + + # ソケットをバインド(procss pid bind) + my $sockaddr_nl = pack_sockaddr_nl($$); + bind( $self->{sock}, $sockaddr_nl ) + or die "Failed to bind Netlink socket: $!"; + + return $self; +} + +sub send_message { + my ( $self, $message ) = @_; + + # カーネル(pid 0)にメッセージを送信 + my $sockaddr_nl = pack_sockaddr_nl(0); + my $bytes_sent = send( $self->{sock}, $message, 0, $sockaddr_nl ); + unless ($bytes_sent) { + die "Failed to send Netlink message: $!"; + } + + return $bytes_sent; +} + +sub receive_message { + my ($self) = @_; + + my $response; + + my $from = recv( $self->{sock}, $response, 8192, 0 ); + unless ( defined $from ) { + die "Failed to receive Netlink response: $!"; + } + + return $response; +} + +sub close { + my ($self) = @_; + close( $self->{sock} ) if $self->{sock}; +} + +# helper functions + +sub get_error_message { + my ($response) = @_; + my $offset = NLMSG_HDRLEN + 4; # After error code + my $len = length($response); + + if ( $len > $offset ) { + my $attr_data = substr( $response, $offset ); + while ( length($attr_data) >= NLA_HDRLEN ) { + my ( $nla_len, $nla_type ) = unpack( 'S S', $attr_data ); + my $payload + = substr( $attr_data, NLA_HDRLEN, $nla_len - NLA_HDRLEN ); + if ( $nla_type == 1 ) { # NLMSGERR_ATTR_MSG + return unpack( 'Z*', $payload ); + } + $attr_data = substr( $attr_data, ( $nla_len + 3 ) & ~3 ); + } + } + return ""; +} + +# cf. https://github.com/torvalds/linux/blob/3efc57369a0ce8f76bf0804f7e673982384e4ac9/include/uapi/linux/netlink.h#L37 +# typedef unsigned short __kernel_sa_family_t; +# struct sockaddr_nl { +# __kernel_sa_family_t nl_family; /* AF_NETLINK */ +# unsigned short nl_pad; /* zero */ +# __u32 nl_pid; /* port ID */ +# __u32 nl_groups; /* multicast groups mask */ +# }; +sub pack_sockaddr_nl { + my ($pid) = @_; + return pack( 'S x2 L L', AF_NETLINK, $pid, 0 ); +} + +# cf. https://github.com/torvalds/linux/blob/3efc57369a0ce8f76bf0804f7e673982384e4ac9/include/uapi/linux/netlink.h#L229 +# /* +# * <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)--> +# * +---------------------+- - -+- - - - - - - - - -+- - -+ +# * | Header | Pad | Payload | Pad | +# * | (struct nlattr) | ing | | ing | +# * +---------------------+- - -+- - - - - - - - - -+- - -+ +# * <-------------- nlattr->nla_len --------------> +# */ +# struct nlattr { +# __u16 nla_len; +# __u16 nla_type; +# }; +sub pack_nlattr { + my ( $type, $payload ) = @_; + my $nla_len = NLA_HDRLEN + length($payload); # ヘッダー+ペイロードの長さ + my $nla_padded_len = ( $nla_len + 3 ) & ~3; # 4バイト境界にアライン + my $padding = "\0" x ( $nla_padded_len - $nla_len ); # パディング + return + pack( 'S S', $nla_padded_len, $type ) + . $payload + . $padding; # パディング後の長さでパック +} + +# cf. https://github.com/torvalds/linux/blob/3efc57369a0ce8f76bf0804f7e673982384e4ac9/include/uapi/linux/netlink.h#L52 +# /** +# * struct nlmsghdr - fixed format metadata header of Netlink messages +# * @nlmsg_len: Length of message including header +# * @nlmsg_type: Message content type +# * @nlmsg_flags: Additional flags +# * @nlmsg_seq: Sequence number +# * @nlmsg_pid: Sending process port ID +# */ +# struct nlmsghdr { +# __u32 nlmsg_len; +# __u16 nlmsg_type; +# __u16 nlmsg_flags; +# __u32 nlmsg_seq; +# __u32 nlmsg_pid; +# }; +sub pack_nlmsghdr { + my ( $len, $type, $flags, $seq, $pid ) = @_; + return pack( 'L S S L L', $len, $type, $flags, $seq, $pid ); +} + +# cf. https://github.com/torvalds/linux/blob/3efc57369a0ce8f76bf0804f7e673982384e4ac9/include/uapi/linux/rtnetlink.h#L561 +# struct ifinfomsg { +# unsigned char ifi_family; +# unsigned char __ifi_pad; +# unsigned short ifi_type; /* ARPHRD_* */ +# int ifi_index; /* Link index */ +# unsigned ifi_flags; /* IFF_* flags */ +# unsigned ifi_change; /* IFF_* change mask */ +# }; +sub pack_ifinfomsg { + my ( $family, $type, $index, $flags, $change ) = @_; + return pack( 'C C S I I I', $family, 0, $type, $index, $flags, $change ); +} + +1; diff --git a/lib/Sys/Ebpf/Link/Netlink/Xdp.pm b/lib/Sys/Ebpf/Link/Netlink/Xdp.pm new file mode 100644 index 0000000..b16162b --- /dev/null +++ b/lib/Sys/Ebpf/Link/Netlink/Xdp.pm @@ -0,0 +1,146 @@ +package Sys::Ebpf::Link::Netlink::Xdp; + +use strict; +use warnings; + +use IO::Interface::Simple (); +use Socket qw( AF_UNSPEC ); +use Errno (); +use Carp qw( croak ); +use Try::Tiny qw( catch finally try ); + +use Sys::Ebpf::Link::Netlink::Socket qw( + pack_nlattr + pack_nlmsghdr + pack_ifinfomsg +); +use Sys::Ebpf::Link::Netlink::Constants::Iflink qw( + IFLA_XDP + IFLA_XDP_FD + IFLA_XDP_FLAGS + XDP_FLAGS_UPDATE_IF_NOEXIST + XDP_FLAGS_DRV_MODE +); +use Sys::Ebpf::Link::Netlink::Constants::Netlink qw( + NLA_F_NESTED + NLM_F_REQUEST + NLM_F_ACK + NLMSG_HDRLEN + NLA_HDRLEN + NETLINK_ROUTE + NLMSG_ERROR + NETLINK_EXT_ACK +); +use Sys::Ebpf::Link::Netlink::Constants::Rtnetlink qw(RTM_SETLINK); + +use Data::HexDump (); + +sub _send_and_recv_netlink_message { + my ( $nlmsg, $nlmsg_seq ) = @_; + my $netlink + = Sys::Ebpf::Link::Netlink::Socket->new( Proto => NETLINK_ROUTE ); + try { + $netlink->send_message($nlmsg); + + my $response = $netlink->receive_message(); + _handle_response( $response, $nlmsg_seq ); + } + catch { + my $error = $_; + croak $error; + } + finally { + $netlink->close(); + }; +} + +sub _handle_response { + my ( $response, $nlmsg_seq ) = @_; + + my $received_bytes = length($response); + + if ( $received_bytes >= NLMSG_HDRLEN ) { + my ( $len, $type, $flags, $seq, $pid ) + = unpack( 'I S S I I', substr( $response, 0, NLMSG_HDRLEN ) ); + if ( $seq != $nlmsg_seq ) { + croak "Netlink response sequence number does not match request"; + } + if ( $type == NLMSG_ERROR ) { + my $error_code + = unpack( 'i', substr( $response, NLMSG_HDRLEN, 4 ) ); + if ( $error_code != 0 ) { + my $error_msg + = Sys::Ebpf::Link::Netlink::get_error_message($response); + croak "Netlink error: $error_code ($error_msg)"; + } + } + } + else { + croak "Received Netlink response is too short"; + } +} + +sub _create_nlmsg { + my ( $ifindex, $prog_fd, $flags ) = @_; + + my $ifinfomsg = pack( 'C C S L L L', AF_UNSPEC, 0, 0, $ifindex, 0, 0 ); + my $nla_fd = pack_nlattr( IFLA_XDP_FD, pack( 'i', $prog_fd ) ); + my $nla_flags = pack_nlattr( IFLA_XDP_FLAGS, pack( 'I', $flags ) ); + my $xdp_attrs = $nla_fd . $nla_flags; + my $nla_xdp = pack_nlattr( NLA_F_NESTED | IFLA_XDP, $xdp_attrs ); + + my $req = $ifinfomsg . $nla_xdp; + my $nlmsg_len = NLMSG_HDRLEN + length($req); + my $nlmsg_seq = int( rand(0xFFFFFFFF) ); + + my $nlmsg + = pack_nlmsghdr( $nlmsg_len, RTM_SETLINK, NLM_F_REQUEST | NLM_F_ACK, + $nlmsg_seq, $$, ) + . $req; + + return { + nlmsg => $nlmsg, + nlmsg_seq => $nlmsg_seq, + }; +} + +sub attach_xdp { + my ( $prog_fd, $ifname, $flags ) = @_; + + # インターフェースのインデックスを取得 + my $iface = IO::Interface::Simple->new($ifname); + unless ($iface) { + croak "Interface $ifname not found"; + } + my $ifindex = $iface->index; + + $flags //= XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE; + + my $nlmsg = _create_nlmsg( $ifindex, $prog_fd, $flags ); + + _send_and_recv_netlink_message( $nlmsg->{nlmsg}, $nlmsg->{nlmsg_seq} ); + + return { + prog_fd => $prog_fd, + ifindex => $ifindex, + ifname => $ifname, + flags => $flags, + }; +} + +sub detach_xdp { + my ( $ifname, $flags ) = @_; + my $iface = IO::Interface::Simple->new($ifname); + unless ($iface) { + croak "Interface $ifname not found"; + } + my $ifindex = $iface->index; + + $flags //= XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE; + + my $nlmsg = _create_nlmsg( $ifindex, -1, $flags ); + + _send_and_recv_netlink_message( $nlmsg->{nlmsg}, $nlmsg->{nlmsg_seq} ); +} + +1; diff --git a/lib/Sys/Ebpf/Loader.pm b/lib/Sys/Ebpf/Loader.pm index cfead3a..dcc4e5f 100644 --- a/lib/Sys/Ebpf/Loader.pm +++ b/lib/Sys/Ebpf/Loader.pm @@ -159,8 +159,8 @@ sub load_bpf_program_from_elf { insns => $bpf_prog, license => $license, log_level => 3, - log_size => 4096 * 16, - log_buf => "\0" x ( 4096 * 16 ), + log_size => 4096 * 256, + log_buf => "\0" x ( 4096 * 256 ), kern_version => 0, prog_flags => 0, }; @@ -237,15 +237,14 @@ sub apply_map_relocations { my $reloc_type = $r_info & 0xFFFFFFFF; # リロケーションタイプを取得 # シンボルテーブルからrelocation対象になり得るシンボル名を取得 - my $symbol - = find_symbol_table_from_idx( $symbols_section, $sym_index ); + my $symbol = $symbols_section->[$sym_index] // undef; if ( !$symbol ) { - print "Symbol not found for index: $sym_index\n"; + print "Symbol Table not found for index: $sym_index\n"; next; } my $sym_name = $symbol->{st_name}; if ( $symbol->{st_shndx} == 0 ) { - print "Symbol not found for index: $sym_index\n"; + print "Symbol Name Table not found for index: $sym_index\n"; next; } diff --git a/sample/kprobe_file_open_counter/kprobe_file_open_counter.c b/sample/kprobe_file_open_counter/kprobe_file_open_counter.c index 41cc916..6903f3d 100644 --- a/sample/kprobe_file_open_counter/kprobe_file_open_counter.c +++ b/sample/kprobe_file_open_counter/kprobe_file_open_counter.c @@ -28,5 +28,3 @@ int kprobe_sysopen() return 0; } - -char LICENSE[] SEC("license") = "GPL"; diff --git a/sample/xdp_dport_counter/build.sh b/sample/xdp_dport_counter/build.sh new file mode 100755 index 0000000..809afb3 --- /dev/null +++ b/sample/xdp_dport_counter/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +clang -O3 -emit-llvm -c xdp_dport_counter.c -o - | llc -march=bpf -filetype=obj -o xdp_dport_counter.o diff --git a/sample/xdp_dport_counter/xdp_dport_counter.c b/sample/xdp_dport_counter/xdp_dport_counter.c new file mode 100644 index 0000000..3a64db2 --- /dev/null +++ b/sample/xdp_dport_counter/xdp_dport_counter.c @@ -0,0 +1,26 @@ +#include +#include + +struct bpf_map_def SEC("maps") xdp_map = { + .type = BPF_MAP_TYPE_ARRAY, + .key_size = sizeof(__u32), + .value_size = sizeof(__u64), + .max_entries = 1, +}; + +SEC("xdp/xdp_pass") +int xdp_pass_func(struct xdp_md *ctx) +{ + __u32 key = 0; + __u64 *value; + + value = bpf_map_lookup_elem(&xdp_map, &key); + if (value) + { + __sync_fetch_and_add(value, 1); + } + + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; diff --git a/sample/xdp_dport_counter/xdp_dport_counter.pl b/sample/xdp_dport_counter/xdp_dport_counter.pl new file mode 100644 index 0000000..e2a0a94 --- /dev/null +++ b/sample/xdp_dport_counter/xdp_dport_counter.pl @@ -0,0 +1,45 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use utf8; +use open ":std", ":encoding(UTF-8)"; +use lib '../../lib'; +use Sys::Ebpf::Loader; +use Sys::Ebpf::Link::Netlink::Xdp; + +my $file = "xdp_dport_counter.o"; +my $loader = Sys::Ebpf::Loader->new($file); +my $data = $loader->load_elf(); + +my $xdp_fn = "xdp/xdp_pass"; + +my ( $map_data, $prog_fd ) = $loader->load_bpf($xdp_fn); +my $map_xdp_map = $map_data->{xdp_map}; +$map_xdp_map->{key_schema} = [ [ 'xdp_map_key', 'uint32' ] ]; +$map_xdp_map->{value_schema} = [ [ 'xdp_map_value', 'uint64' ] ]; + +# インターフェース名を指定 +my $ifname = "ens3"; +Sys::Ebpf::Link::Netlink::Xdp::detach_xdp($ifname); +my $xdp_info = Sys::Ebpf::Link::Netlink::Xdp::attach_xdp( $prog_fd, $ifname ); + +print "Map FD: " . $map_xdp_map->{map_fd} . "\n"; +print "Program FD: $prog_fd\n"; +print $xdp_info->{ifname} . " にXDPプログラムをアタッチしました。\n"; +print "XDPプログラムをアタッチしました。Ctrl+Cで停止します。\n"; + +while (1) { + my $key = { xdp_map_key => 0 }; + my $value = $map_xdp_map->lookup($key); + if ( defined $value ) { + printf "パケット数: %d\n", $value->{xdp_map_value}; + } + sleep(1); +} + +END { + if ($xdp_info) { + Sys::Ebpf::Link::Netlink::Xdp::detach_xdp($xdp_info); + } +}