Skip to content

Commit de018aa

Browse files
committed
ext/sockets: follow-up on AF_PACKET support.
support from socket_recvfrom and exposing basic PHP userland type. close GH-17657
1 parent 98fc344 commit de018aa

File tree

7 files changed

+245
-20
lines changed

7 files changed

+245
-20
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ PHP NEWS
145145
. socket_getsockname/socket_create/socket_bind handled AF_PACKET family socket.
146146
(David Carlier)
147147
. Added IP_BINDANY for a socket to bind to any address. (David Carlier)
148+
. Added support for ethernet frames data for socket_sendto/socket_recvfrom
149+
for SOCK_RAW sockets. (David Carlier)
148150

149151
- Sodium:
150152
. Fix overall theorical overflows on zend_string buffer allocations.

ext/sockets/config.m4

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ PHP_ARG_ENABLE([sockets],
55

66
if test "$PHP_SOCKETS" != "no"; then
77
AC_CHECK_FUNCS([hstrerror if_nametoindex if_indextoname sockatmark])
8-
AC_CHECK_HEADERS([sys/sockio.h linux/filter.h linux/if_packet.h linux/if_ether.h])
8+
AC_CHECK_HEADERS([sys/sockio.h linux/filter.h linux/if_packet.h linux/if_ether.h netinet/ether.h])
99
AC_DEFINE([HAVE_SOCKETS], [1],
1010
[Define to 1 if the PHP extension 'sockets' is available.])
1111

ext/sockets/php_sockets.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ typedef struct {
7777

7878
extern PHP_SOCKETS_API zend_class_entry *socket_ce;
7979

80+
#ifdef AF_PACKET
81+
extern PHP_SOCKETS_API zend_class_entry *socket_ethinfo_ce;
82+
#endif
83+
8084
static inline php_socket *socket_from_obj(zend_object *obj) {
8185
return (php_socket *)((char *)(obj) - XtOffsetOf(php_socket, std));
8286
}

ext/sockets/sockets.c

Lines changed: 138 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
# include <netdb.h>
4242
# include <netinet/in.h>
4343
# include <netinet/tcp.h>
44+
# include <netinet/udp.h>
4445
# include <sys/un.h>
4546
# include <arpa/inet.h>
4647
# include <sys/time.h>
@@ -54,6 +55,11 @@
5455
# ifdef HAVE_IF_NAMETOINDEX
5556
# include <net/if.h>
5657
# endif
58+
# ifdef HAVE_NETINET_ETHER_H
59+
# include <netinet/ether.h>
60+
# include <netinet/ip.h>
61+
# include <linux/ipv6.h>
62+
# endif
5763
# if defined(HAVE_LINUX_SOCK_DIAG_H)
5864
# include <linux/sock_diag.h>
5965
# else
@@ -120,6 +126,9 @@ static PHP_RSHUTDOWN_FUNCTION(sockets);
120126

121127
zend_class_entry *socket_ce;
122128
static zend_object_handlers socket_object_handlers;
129+
#ifdef AF_PACKET
130+
zend_class_entry *socket_ethinfo_ce;
131+
#endif
123132

124133
static zend_object *socket_create_object(zend_class_entry *class_type) {
125134
php_socket *intern = zend_object_alloc(sizeof(php_socket), class_type);
@@ -482,6 +491,9 @@ static PHP_MINIT_FUNCTION(sockets)
482491
socket_object_handlers.get_gc = socket_get_gc;
483492
socket_object_handlers.compare = zend_objects_not_comparable;
484493

494+
#if defined(AF_PACKET)
495+
socket_ethinfo_ce = register_class_SocketEthernetInfo();
496+
#endif
485497
address_info_ce = register_class_AddressInfo();
486498
address_info_ce->create_object = address_info_create_object;
487499
address_info_ce->default_object_handlers = &address_info_object_handlers;
@@ -1388,7 +1400,7 @@ PHP_FUNCTION(socket_bind)
13881400
struct sockaddr_ll *sa = (struct sockaddr_ll *) sock_type;
13891401
socklen_t sa_len = sizeof(sa);
13901402

1391-
if (getsockname(php_sock->bsd_socket, sock_type, &sa_len) < 0) {
1403+
if (getsockname(php_sock->bsd_socket, (struct sockaddr *)sa, &sa_len) < 0) {
13921404
zend_value_error("invalid AF_PACKET socket");
13931405
RETURN_THROWS();
13941406
}
@@ -1503,7 +1515,9 @@ PHP_FUNCTION(socket_recvfrom)
15031515
struct sockaddr_in6 sin6;
15041516
#endif
15051517
#ifdef AF_PACKET
1506-
//struct sockaddr_ll sll;
1518+
struct sockaddr_ll sll;
1519+
int protoid;
1520+
socklen_t protoidlen = sizeof(protoid);
15071521
#endif
15081522
char addrbuf[INET6_ADDRSTRLEN];
15091523
socklen_t slen;
@@ -1532,6 +1546,15 @@ PHP_FUNCTION(socket_recvfrom)
15321546
RETURN_FALSE;
15331547
}
15341548

1549+
#ifdef AF_PACKET
1550+
// ethernet header + payload
1551+
// possibly follow-up PR SOCK_DGRAM
1552+
if (php_sock->type == AF_PACKET && arg3 < 60) {
1553+
zend_argument_value_error(3, "must be at least 60 for AF_PACKET");
1554+
RETURN_THROWS();
1555+
}
1556+
#endif
1557+
15351558
recv_buf = zend_string_alloc(arg3 + 1, 0);
15361559

15371560
switch (php_sock->type) {
@@ -1610,14 +1633,19 @@ PHP_FUNCTION(socket_recvfrom)
16101633
break;
16111634
#endif
16121635
#ifdef AF_PACKET
1613-
/*
16141636
case AF_PACKET:
1615-
// TODO expose and use proper ethernet frame type instead i.e. src mac, dst mac and payload to userland
1616-
// ditto for socket_sendto
1637+
getsockopt(php_sock->bsd_socket, SOL_SOCKET, SO_TYPE, (char *) &protoid, &protoidlen);
1638+
1639+
// TODO: SOCK_DGRAM support
1640+
if (protoid != SOCK_RAW) {
1641+
zend_argument_value_error(1, "must be SOCK_RAW socket type");
1642+
RETURN_THROWS();
1643+
}
16171644
slen = sizeof(sll);
16181645
memset(&sll, 0, sizeof(sll));
16191646
sll.sll_family = AF_PACKET;
16201647
char ifrname[IFNAMSIZ];
1648+
zval zpayload;
16211649

16221650
retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&sll, (socklen_t *)&slen);
16231651

@@ -1626,20 +1654,93 @@ PHP_FUNCTION(socket_recvfrom)
16261654
zend_string_efree(recv_buf);
16271655
RETURN_FALSE;
16281656
}
1629-
ZSTR_LEN(recv_buf) = retval;
1630-
ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0';
16311657

16321658
if (UNEXPECTED(!if_indextoname(sll.sll_ifindex, ifrname))) {
16331659
PHP_SOCKET_ERROR(php_sock, "unable to get the interface name", errno);
16341660
zend_string_efree(recv_buf);
16351661
RETURN_FALSE;
16361662
}
16371663

1638-
ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf);
1664+
struct ethhdr *e = (struct ethhdr *)ZSTR_VAL(recv_buf);
1665+
unsigned short protocol = ntohs(e->h_proto);
1666+
unsigned char *payload;
1667+
1668+
zval obj;
1669+
object_init_ex(&obj, socket_ethinfo_ce);
1670+
array_init(&zpayload);
1671+
1672+
switch (protocol) {
1673+
case ETH_P_IP: {
1674+
payload = ((unsigned char *)e + sizeof(struct ethhdr));
1675+
struct iphdr *ip = (struct iphdr *)payload;
1676+
unsigned char *ipdata = payload + (ip->ihl * 4);
1677+
struct in_addr s, d;
1678+
s.s_addr = ip->saddr;
1679+
d.s_addr = ip->daddr;
1680+
add_assoc_string(&zpayload, "ipsrc", inet_ntoa(s));
1681+
add_assoc_string(&zpayload, "ipdst", inet_ntoa(d));
1682+
1683+
switch (ip->protocol) {
1684+
case IPPROTO_TCP: {
1685+
struct tcphdr *tcp = (struct tcphdr *)ipdata;
1686+
add_assoc_long(&zpayload, "portsrc", ntohs(tcp->th_sport));
1687+
add_assoc_long(&zpayload, "portdst", ntohs(tcp->th_dport));
1688+
break;
1689+
}
1690+
case IPPROTO_UDP: {
1691+
struct udphdr *udp = (struct udphdr *)ipdata;
1692+
add_assoc_long(&zpayload, "portsrc", ntohs(udp->uh_sport));
1693+
add_assoc_long(&zpayload, "portdst", ntohs(udp->uh_dport));
1694+
break;
1695+
}
1696+
default:
1697+
zend_string_efree(recv_buf);
1698+
zval_ptr_dtor(&zpayload);
1699+
zval_ptr_dtor(&obj);
1700+
zend_value_error("unsupported ip header protocol");
1701+
RETURN_THROWS();
1702+
}
1703+
break;
1704+
}
1705+
case ETH_P_IPV6: {
1706+
payload = ((unsigned char *)e + sizeof(struct ethhdr));
1707+
struct ipv6hdr *ip = (struct ipv6hdr *)payload;
1708+
char s[INET6_ADDRSTRLEN], d[INET6_ADDRSTRLEN];
1709+
inet_ntop(AF_INET6, &ip->saddr, s, sizeof(s));
1710+
inet_ntop(AF_INET6, &ip->daddr, d, sizeof(d));
1711+
add_assoc_string(&zpayload, "ipsrc", s);
1712+
add_assoc_string(&zpayload, "ipdst", d);
1713+
break;
1714+
}
1715+
case ETH_P_LOOP: {
1716+
struct ethhdr *innere = (struct ethhdr *)((unsigned char *)e + ETH_HLEN);
1717+
add_assoc_string(&zpayload, "macsrc", ether_ntoa((struct ether_addr *)innere->h_source));
1718+
add_assoc_string(&zpayload, "macdst", ether_ntoa((struct ether_addr *)innere->h_dest));
1719+
break;
1720+
}
1721+
default:
1722+
zend_string_efree(recv_buf);
1723+
zval_ptr_dtor(&zpayload);
1724+
zval_ptr_dtor(&obj);
1725+
zend_value_error("unsupported ethernet protocol");
1726+
RETURN_THROWS();
1727+
}
1728+
1729+
Z_DELREF(zpayload);
1730+
zend_string_efree(recv_buf);
1731+
zend_update_property(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("socket"), arg1);
1732+
zend_update_property_string(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("macsrc"), ether_ntoa((struct ether_addr *)e->h_source));
1733+
zend_update_property_string(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("macdst"), ether_ntoa((struct ether_addr *)e->h_dest));
1734+
zend_update_property_long(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("ethprotocol"), protocol);
1735+
zend_update_property(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("payload"), &zpayload);
1736+
1737+
ZEND_TRY_ASSIGN_REF_VALUE(arg2, &obj);
16391738
ZEND_TRY_ASSIGN_REF_STRING(arg5, ifrname);
1640-
ZEND_TRY_ASSIGN_REF_LONG(arg6, sll.sll_ifindex);
1739+
1740+
if (arg6) {
1741+
ZEND_TRY_ASSIGN_REF_LONG(arg6, sll.sll_ifindex);
1742+
}
16411743
break;
1642-
*/
16431744
#endif
16441745
default:
16451746
zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6");
@@ -1661,7 +1762,10 @@ PHP_FUNCTION(socket_sendto)
16611762
struct sockaddr_in6 sin6;
16621763
#endif
16631764
#ifdef AF_PACKET
1664-
//struct sockaddr_ll sll;
1765+
struct sockaddr_ll sll;
1766+
unsigned char halen;
1767+
int protoid;
1768+
socklen_t protoidlen = sizeof(protoid);
16651769
#endif
16661770
int retval;
16671771
size_t buf_len;
@@ -1694,6 +1798,15 @@ PHP_FUNCTION(socket_sendto)
16941798
RETURN_THROWS();
16951799
}
16961800

1801+
#ifdef AF_PACKET
1802+
// ether header + payload
1803+
// TODO dealing with SOCK_DGRAM
1804+
if (php_sock->type == AF_PACKET && len < 60) {
1805+
zend_argument_value_error(3, "must be at least 64 for AF_PACKET");
1806+
RETURN_THROWS();
1807+
}
1808+
#endif
1809+
16971810
switch (php_sock->type) {
16981811
case AF_UNIX:
16991812
memset(&s_un, 0, sizeof(s_un));
@@ -1738,23 +1851,33 @@ PHP_FUNCTION(socket_sendto)
17381851
break;
17391852
#endif
17401853
#ifdef AF_PACKET
1741-
/*
17421854
case AF_PACKET:
1855+
getsockopt(php_sock->bsd_socket, SOL_SOCKET, SO_TYPE, (char *) &protoid, &protoidlen);
1856+
1857+
// TODO: SOCK_DGRAM support
1858+
if (protoid != SOCK_RAW) {
1859+
zend_argument_value_error(1, "must be SOCK_RAW socket type");
1860+
RETURN_THROWS();
1861+
}
17431862
if (port_is_null) {
17441863
zend_argument_value_error(6, "cannot be null when the socket type is AF_PACKET");
17451864
RETURN_THROWS();
17461865
}
17471866

1867+
halen = ZSTR_LEN(addr) > ETH_ALEN ? ETH_ALEN : (unsigned char)ZSTR_LEN(addr);
1868+
17481869
memset(&sll, 0, sizeof(sll));
1870+
memcpy(sll.sll_addr, addr, halen);
17491871
sll.sll_family = AF_PACKET;
17501872
sll.sll_ifindex = port;
1873+
sll.sll_halen = halen;
17511874

1752-
retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *) &sin, sizeof(sin));
1875+
// TODO allows to use more user friendly type to replace raw buffer usage
1876+
retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *) &sll, sizeof(sll));
17531877
break;
1754-
*/
17551878
#endif
17561879
default:
1757-
zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6");
1880+
zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, AF_PACKET or AF_INET6");
17581881
RETURN_THROWS();
17591882
}
17601883

@@ -2880,8 +3003,6 @@ PHP_FUNCTION(socket_addrinfo_connect)
28803003

28813004
ai = Z_ADDRESS_INFO_P(arg1);
28823005

2883-
PHP_ETH_PROTO_CHECK(ai->addrinfo.ai_protocol, ai->addrinfo.ai_family);
2884-
28853006
object_init_ex(return_value, socket_ce);
28863007
php_sock = Z_SOCKET_P(return_value);
28873008

ext/sockets/sockets.stub.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2013,6 +2013,11 @@
20132013
* @cvalue ETH_P_ALL
20142014
*/
20152015
const ETH_P_ALL = UNKNOWN;
2016+
/**
2017+
* @var int
2018+
* @cvalue ETH_FRAME_LEN
2019+
*/
2020+
const ETH_FRAME_LEN = UNKNOWN;
20162021
#endif
20172022

20182023
/**
@@ -2156,3 +2161,19 @@ function socket_wsaprotocol_info_import(string $info_id): Socket|false {}
21562161

21572162
function socket_wsaprotocol_info_release(string $info_id): bool {}
21582163
#endif
2164+
2165+
#ifdef AF_PACKET
2166+
final class SocketEthernetInfo
2167+
{
2168+
/** @readonly **/
2169+
public Socket $socket;
2170+
/** @readonly **/
2171+
public int $ethprotocol;
2172+
/** @readonly **/
2173+
public string $macsrc;
2174+
/** @readonly **/
2175+
public string $macdst;
2176+
/** @readonly **/
2177+
public array $payload;
2178+
}
2179+
#endif

ext/sockets/sockets_arginfo.h

Lines changed: 47 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)