diff --git a/ext/sockets/config.m4 b/ext/sockets/config.m4 index 37f927a78186a..a4a6e55a7926b 100644 --- a/ext/sockets/config.m4 +++ b/ext/sockets/config.m4 @@ -5,7 +5,7 @@ PHP_ARG_ENABLE([sockets], if test "$PHP_SOCKETS" != "no"; then AC_CHECK_FUNCS([hstrerror if_nametoindex if_indextoname sockatmark]) - AC_CHECK_HEADERS([sys/sockio.h linux/filter.h linux/if_packet.h linux/if_ether.h]) + AC_CHECK_HEADERS([sys/sockio.h linux/filter.h linux/if_packet.h linux/if_ether.h netinet/ether.h]) AC_DEFINE([HAVE_SOCKETS], [1], [Define to 1 if the PHP extension 'sockets' is available.]) diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 89c613ba43074..5e4c582788caf 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -77,6 +77,15 @@ typedef struct { extern PHP_SOCKETS_API zend_class_entry *socket_ce; +#ifdef AF_PACKET +extern PHP_SOCKETS_API zend_class_entry *packet_ce; +extern PHP_SOCKETS_API zend_class_entry *ethinfopacket_ce; +extern PHP_SOCKETS_API zend_class_entry *tcppacket_ce; +extern PHP_SOCKETS_API zend_class_entry *udppacket_ce; +extern PHP_SOCKETS_API zend_class_entry *ipv4packet_ce; +extern PHP_SOCKETS_API zend_class_entry *ipv6packet_ce; +#endif + static inline php_socket *socket_from_obj(zend_object *obj) { return (php_socket *)((char *)(obj) - XtOffsetOf(php_socket, std)); } diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 2d8b55c6b97ba..b8a1fbebc6351 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -41,6 +41,7 @@ # include # include # include +# include # include # include # include @@ -54,6 +55,11 @@ # ifdef HAVE_IF_NAMETOINDEX # include # endif +# ifdef HAVE_NETINET_ETHER_H +# include +# include +# include +# endif # if defined(HAVE_LINUX_SOCK_DIAG_H) # include # else @@ -120,6 +126,14 @@ static PHP_RSHUTDOWN_FUNCTION(sockets); zend_class_entry *socket_ce; static zend_object_handlers socket_object_handlers; +#ifdef AF_PACKET +zend_class_entry *packet_ce; +zend_class_entry *ethpacket_ce; +zend_class_entry *tcppacket_ce; +zend_class_entry *udppacket_ce; +zend_class_entry *ipv4packet_ce; +zend_class_entry *ipv6packet_ce; +#endif static zend_object *socket_create_object(zend_class_entry *class_type) { php_socket *intern = zend_object_alloc(sizeof(php_socket), class_type); @@ -482,6 +496,14 @@ static PHP_MINIT_FUNCTION(sockets) socket_object_handlers.get_gc = socket_get_gc; socket_object_handlers.compare = zend_objects_not_comparable; +#if defined(AF_PACKET) + packet_ce = register_class_Socket_Packet(); + ethpacket_ce = register_class_Socket_EthernetPacket(packet_ce); + tcppacket_ce = register_class_Socket_TcpPacket(packet_ce); + udppacket_ce = register_class_Socket_UdpPacket(packet_ce); + ipv4packet_ce = register_class_Socket_Ipv4Packet(packet_ce); + ipv6packet_ce = register_class_Socket_Ipv6Packet(packet_ce); +#endif address_info_ce = register_class_AddressInfo(); address_info_ce->create_object = address_info_create_object; address_info_ce->default_object_handlers = &address_info_object_handlers; @@ -1388,7 +1410,7 @@ PHP_FUNCTION(socket_bind) struct sockaddr_ll *sa = (struct sockaddr_ll *) sock_type; socklen_t sa_len = sizeof(sa); - if (getsockname(php_sock->bsd_socket, sock_type, &sa_len) < 0) { + if (getsockname(php_sock->bsd_socket, (struct sockaddr *)sa, &sa_len) < 0) { zend_value_error("invalid AF_PACKET socket"); RETURN_THROWS(); } @@ -1413,6 +1435,55 @@ PHP_FUNCTION(socket_bind) } /* }}} */ +#ifdef AF_PACKET +#define ETH_SUB_CHECKLENGTH(a, lyr) \ + do { \ + if ((char *)ipdata + sizeof(a) < ZSTR_VAL(recv_buf) + slen) { \ + zend_string_efree(recv_buf); \ + Z_DELREF_P(zpayload); \ + ZEND_TRY_ASSIGN_REF_VALUE(arg2, obj); \ + ZEND_TRY_ASSIGN_REF_STRING(arg5, ifrname); \ + if (arg6) { \ + ZEND_TRY_ASSIGN_REF_LONG(arg6, sll.sll_ifindex);\ + } \ + zend_value_error("invalid %s header", lyr); \ + return FAILURE; \ + } \ + } while (0) +static zend_result php_socket_afpacket_add_tcp(unsigned char *ipdata, struct sockaddr_ll sll, char *ifrname, zend_string *recv_buf, + size_t slen, zval *szpayload, zval *zpayload, zval *obj, zval *arg2, zval *arg5, zval *arg6) { + struct tcphdr a; + ETH_SUB_CHECKLENGTH(a, "TCP"); + memcpy(&a, ipdata, sizeof(a)); + struct tcphdr *tcp = &a; + object_init_ex(szpayload, tcppacket_ce); + zend_update_property_long(Z_OBJCE_P(szpayload), Z_OBJ_P(szpayload), ZEND_STRL("srcPort"), ntohs(tcp->th_sport)); + zend_update_property_long(Z_OBJCE_P(szpayload), Z_OBJ_P(szpayload), ZEND_STRL("dstPort"), ntohs(tcp->th_dport)); + zend_update_property_long(Z_OBJCE_P(szpayload), Z_OBJ_P(szpayload), ZEND_STRL("headerSize"), sizeof(*tcp)); + zend_update_property_stringl(Z_OBJCE_P(szpayload), Z_OBJ_P(szpayload), ZEND_STRL("rawPacket"), (char *)ipdata, sizeof(*tcp)); + zend_update_property(Z_OBJCE_P(zpayload), Z_OBJ_P(zpayload), ZEND_STRL("payload"), szpayload); + Z_DELREF_P(szpayload); + return SUCCESS; +} + +static zend_result php_socket_afpacket_add_udp(unsigned char *ipdata, struct sockaddr_ll sll, char *ifrname, zend_string *recv_buf, + size_t slen, zval *szpayload, zval *zpayload, zval *obj, zval *arg2, zval *arg5, zval *arg6) { + struct udphdr a; + ETH_SUB_CHECKLENGTH(a, "UDP"); + memcpy(&a, ipdata, sizeof(a)); + struct udphdr *udp = &a; + size_t headersize = sizeof(*udp); + object_init_ex(szpayload, udppacket_ce); + zend_update_property_long(Z_OBJCE_P(szpayload), Z_OBJ_P(szpayload), ZEND_STRL("srcPort"), ntohs(udp->uh_sport)); + zend_update_property_long(Z_OBJCE_P(szpayload), Z_OBJ_P(szpayload), ZEND_STRL("dstPort"), ntohs(udp->uh_dport)); + zend_update_property_long(Z_OBJCE_P(szpayload), Z_OBJ_P(szpayload), ZEND_STRL("headerSize"), headersize); + zend_update_property_stringl(Z_OBJCE_P(szpayload), Z_OBJ_P(szpayload), ZEND_STRL("rawPacket"), (char *)ipdata, headersize); + zend_update_property(Z_OBJCE_P(zpayload), Z_OBJ_P(zpayload), ZEND_STRL("payload"), szpayload); + Z_DELREF_P(szpayload); + return SUCCESS; +} +#endif + /* {{{ Receives data from a connected socket */ PHP_FUNCTION(socket_recv) { @@ -1503,7 +1574,9 @@ PHP_FUNCTION(socket_recvfrom) struct sockaddr_in6 sin6; #endif #ifdef AF_PACKET - //struct sockaddr_ll sll; + struct sockaddr_ll sll; + int protoid; + socklen_t protoidlen = sizeof(protoid); #endif char addrbuf[INET6_ADDRSTRLEN]; socklen_t slen; @@ -1532,6 +1605,15 @@ PHP_FUNCTION(socket_recvfrom) RETURN_FALSE; } +#ifdef AF_PACKET + // ethernet header + payload + // possibly follow-up PR SOCK_DGRAM + if (php_sock->type == AF_PACKET && arg3 < 60) { + zend_argument_value_error(3, "must be at least 60 for AF_PACKET"); + RETURN_THROWS(); + } +#endif + recv_buf = zend_string_alloc(arg3 + 1, 0); switch (php_sock->type) { @@ -1610,14 +1692,19 @@ PHP_FUNCTION(socket_recvfrom) break; #endif #ifdef AF_PACKET - /* case AF_PACKET: - // TODO expose and use proper ethernet frame type instead i.e. src mac, dst mac and payload to userland - // ditto for socket_sendto + getsockopt(php_sock->bsd_socket, SOL_SOCKET, SO_TYPE, (char *) &protoid, &protoidlen); + + // TODO: SOCK_DGRAM support + if (protoid != SOCK_RAW) { + zend_argument_value_error(1, "must be SOCK_RAW socket type"); + RETURN_THROWS(); + } slen = sizeof(sll); memset(&sll, 0, sizeof(sll)); sll.sll_family = AF_PACKET; char ifrname[IFNAMSIZ]; + zval zpayload; retval = recvfrom(php_sock->bsd_socket, ZSTR_VAL(recv_buf), arg3, arg4, (struct sockaddr *)&sll, (socklen_t *)&slen); @@ -1626,8 +1713,12 @@ PHP_FUNCTION(socket_recvfrom) zend_string_efree(recv_buf); RETURN_FALSE; } - ZSTR_LEN(recv_buf) = retval; - ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\0'; + + if (UNEXPECTED(slen < ETH_HLEN)) { + zend_value_error("invalid ethernet frame buffer length"); + zend_string_efree(recv_buf); + RETURN_THROWS(); + } if (UNEXPECTED(!if_indextoname(sll.sll_ifindex, ifrname))) { PHP_SOCKET_ERROR(php_sock, "unable to get the interface name", errno); @@ -1635,11 +1726,199 @@ PHP_FUNCTION(socket_recvfrom) RETURN_FALSE; } - ZEND_TRY_ASSIGN_REF_NEW_STR(arg2, recv_buf); + struct ethhdr *e = (struct ethhdr *)ZSTR_VAL(recv_buf); + unsigned short protocol = ntohs(e->h_proto); + unsigned char *payload; + + zval obj; + object_init_ex(&obj, ethpacket_ce); + zend_update_property(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("socket"), arg1); + zend_update_property_long(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("headerSize"), ETH_HLEN); + zend_update_property_long(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("ethProtocol"), protocol); + + + switch (protocol) { + case ETH_P_IP: { + payload = ((unsigned char *)e + ETH_HLEN); + struct iphdr a; + memcpy(&a, payload, sizeof(a)); + struct iphdr *ip = &a; + size_t tlayer = ip->ihl * 4; + size_t totalip = ntohs(ip->tot_len); + + if (tlayer < sizeof(*ip) || totalip < tlayer || totalip < slen) { + ZVAL_NULL(&zpayload); + zend_update_property(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("payload"), &zpayload); + zend_update_property_string(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("rawPacket"), ZSTR_VAL(recv_buf)); + zend_string_efree(recv_buf); + ZEND_TRY_ASSIGN_REF_VALUE(arg2, &obj); + ZEND_TRY_ASSIGN_REF_STRING(arg5, ifrname); + + if (arg6) { + ZEND_TRY_ASSIGN_REF_LONG(arg6, sll.sll_ifindex); + } + zend_value_error("invalid transport header length"); + RETURN_THROWS(); + } + unsigned char *ipdata = payload + tlayer; + struct in_addr s, d; + s.s_addr = ip->saddr; + d.s_addr = ip->daddr; + zval szpayload; + object_init_ex(&zpayload, ipv4packet_ce); + zend_update_property_string(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("srcAddr"), inet_ntoa(s)); + zend_update_property_string(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("dstAddr"), inet_ntoa(d)); + zend_update_property_long(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("headerSize"), totalip); + zend_update_property_stringl(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("rawPacket"), (char *)payload, totalip); + + switch (ip->protocol) { + case IPPROTO_TCP: { + if (php_socket_afpacket_add_tcp(ipdata, sll, ifrname, recv_buf, slen, &szpayload, &zpayload, &obj, arg2, arg5, arg6) == FAILURE) { + RETURN_THROWS(); + } + break; + } + case IPPROTO_UDP: { + if (php_socket_afpacket_add_udp(ipdata, sll, ifrname, recv_buf, slen, &szpayload, &zpayload, &obj, arg2, arg5, arg6) == FAILURE) { + RETURN_THROWS(); + } + break; + } + default: + zend_update_property(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("payload"), &zpayload); + zend_update_property_string(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("rawPacket"), ZSTR_VAL(recv_buf)); + zend_string_efree(recv_buf); + Z_DELREF(zpayload); + ZEND_TRY_ASSIGN_REF_VALUE(arg2, &obj); + ZEND_TRY_ASSIGN_REF_STRING(arg5, ifrname); + + if (arg6) { + ZEND_TRY_ASSIGN_REF_LONG(arg6, sll.sll_ifindex); + } + zend_value_error("unsupported ip header protocol"); + RETURN_THROWS(); + } + break; + } + case ETH_P_IPV6: { + payload = ((unsigned char *)e + ETH_HLEN); + struct ipv6hdr a; + memcpy(&a, payload, sizeof(a)); + struct ipv6hdr *ip = &a; + size_t totalip = sizeof(*ip) + ip->payload_len; + if (totalip < slen) { + ZVAL_NULL(&zpayload); + zend_update_property(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("payload"), &zpayload); + zend_update_property_string(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("rawPacket"), ZSTR_VAL(recv_buf)); + zend_string_efree(recv_buf); + ZEND_TRY_ASSIGN_REF_VALUE(arg2, &obj); + ZEND_TRY_ASSIGN_REF_STRING(arg5, ifrname); + + if (arg6) { + ZEND_TRY_ASSIGN_REF_LONG(arg6, sll.sll_ifindex); + } + zend_value_error("invalid transport header length"); + RETURN_THROWS(); + } + char s[INET6_ADDRSTRLEN], d[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &ip->saddr, s, sizeof(s)); + inet_ntop(AF_INET6, &ip->daddr, d, sizeof(d)); + zval szpayload; + object_init_ex(&zpayload, ipv6packet_ce); + zend_update_property_string(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("srcAddr"), s); + zend_update_property_string(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("dstAddr"), d); + zend_update_property_long(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("headerSize"), totalip); + zend_update_property_stringl(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("rawPacket"), (char *)payload, totalip); + unsigned char ipprotocol = ip->nexthdr; + unsigned char *ipdata = payload + sizeof(*ip); + + switch (ipprotocol) { + case IPPROTO_TCP: { + if (php_socket_afpacket_add_tcp(ipdata, sll, ifrname, recv_buf, slen, &szpayload, &zpayload, &obj, arg2, arg5, arg6) == FAILURE) { + RETURN_THROWS(); + } + break; + } + case IPPROTO_UDP: { + if (php_socket_afpacket_add_udp(ipdata, sll, ifrname, recv_buf, slen, &szpayload, &zpayload, &obj, arg2, arg5, arg6) == FAILURE) { + RETURN_THROWS(); + } + break; + } + // TODO IPPROTO_ICMPV6 support + default: + zend_update_property(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("payload"), &zpayload); + zend_update_property_string(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("rawPacket"), ZSTR_VAL(recv_buf)); + zend_string_efree(recv_buf); + Z_DELREF(zpayload); + ZEND_TRY_ASSIGN_REF_VALUE(arg2, &obj); + ZEND_TRY_ASSIGN_REF_STRING(arg5, ifrname); + + if (arg6) { + ZEND_TRY_ASSIGN_REF_LONG(arg6, sll.sll_ifindex); + } + zend_value_error("unsupported ip header protocol"); + RETURN_THROWS(); + } + break; + } + case ETH_P_LOOP: { + payload = ((unsigned char *)e + ETH_HLEN); + struct ethhdr a; + if ((char *)payload + sizeof(a) < ZSTR_VAL(recv_buf) + slen) { + zend_string_efree(recv_buf); + Z_DELREF(zpayload); + ZEND_TRY_ASSIGN_REF_VALUE(arg2, &obj); + ZEND_TRY_ASSIGN_REF_STRING(arg5, ifrname); + if (arg6) { + ZEND_TRY_ASSIGN_REF_LONG(arg6, sll.sll_ifindex); + } + zend_value_error("invalid LOOP header"); + RETURN_THROWS(); + } + memcpy(&a, payload, sizeof(a)); + struct ethhdr *innere = &a; + zval innerp; + ZVAL_NULL(&innerp); + object_init_ex(&zpayload, ethpacket_ce); + zend_update_property_string(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("srcMac"), ether_ntoa((struct ether_addr *)innere->h_source)); + zend_update_property_string(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("dstMac"), ether_ntoa((struct ether_addr *)innere->h_dest)); + zend_update_property_long(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("headerSize"), ETH_HLEN); + zend_update_property(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("socket"), arg1); + zend_update_property(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("rawPacket"), &innerp); + zend_update_property(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("payload"), &innerp); + zend_update_property_long(Z_OBJCE(zpayload), Z_OBJ(zpayload), ZEND_STRL("ethProtocol"), 0); + break; + } + default: + ZVAL_NULL(&zpayload); + zend_update_property(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("payload"), &zpayload); + zend_update_property_string(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("rawPacket"), ZSTR_VAL(recv_buf)); + zend_string_efree(recv_buf); + ZEND_TRY_ASSIGN_REF_VALUE(arg2, &obj); + ZEND_TRY_ASSIGN_REF_STRING(arg5, ifrname); + + if (arg6) { + ZEND_TRY_ASSIGN_REF_LONG(arg6, sll.sll_ifindex); + } + zend_value_error("unsupported ethernet protocol"); + RETURN_THROWS(); + } + + zend_update_property_string(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("srcMac"), ether_ntoa((struct ether_addr *)e->h_source)); + zend_update_property_string(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("dstMac"), ether_ntoa((struct ether_addr *)e->h_dest)); + zend_update_property(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("payload"), &zpayload); + zend_update_property_string(Z_OBJCE(obj), Z_OBJ(obj), ZEND_STRL("rawPacket"), ZSTR_VAL(recv_buf)); + Z_DELREF(zpayload); + zend_string_efree(recv_buf); + + ZEND_TRY_ASSIGN_REF_VALUE(arg2, &obj); ZEND_TRY_ASSIGN_REF_STRING(arg5, ifrname); - ZEND_TRY_ASSIGN_REF_LONG(arg6, sll.sll_ifindex); + + if (arg6) { + ZEND_TRY_ASSIGN_REF_LONG(arg6, sll.sll_ifindex); + } break; - */ #endif default: zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6"); @@ -1661,7 +1940,10 @@ PHP_FUNCTION(socket_sendto) struct sockaddr_in6 sin6; #endif #ifdef AF_PACKET - //struct sockaddr_ll sll; + struct sockaddr_ll sll; + unsigned char halen; + int protoid; + socklen_t protoidlen = sizeof(protoid); #endif int retval; size_t buf_len; @@ -1694,6 +1976,15 @@ PHP_FUNCTION(socket_sendto) RETURN_THROWS(); } +#ifdef AF_PACKET + // ether header + payload + // TODO dealing with SOCK_DGRAM + if (php_sock->type == AF_PACKET && len < 60) { + zend_argument_value_error(3, "must be at least 64 for AF_PACKET"); + RETURN_THROWS(); + } +#endif + switch (php_sock->type) { case AF_UNIX: memset(&s_un, 0, sizeof(s_un)); @@ -1738,23 +2029,33 @@ PHP_FUNCTION(socket_sendto) break; #endif #ifdef AF_PACKET - /* case AF_PACKET: + getsockopt(php_sock->bsd_socket, SOL_SOCKET, SO_TYPE, (char *) &protoid, &protoidlen); + + // TODO: SOCK_DGRAM support + if (protoid != SOCK_RAW) { + zend_argument_value_error(1, "must be SOCK_RAW socket type"); + RETURN_THROWS(); + } if (port_is_null) { zend_argument_value_error(6, "cannot be null when the socket type is AF_PACKET"); RETURN_THROWS(); } + halen = ZSTR_LEN(addr) > ETH_ALEN ? ETH_ALEN : (unsigned char)ZSTR_LEN(addr); + memset(&sll, 0, sizeof(sll)); + memcpy(sll.sll_addr, addr, halen); sll.sll_family = AF_PACKET; sll.sll_ifindex = port; + sll.sll_halen = halen; - retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *) &sin, sizeof(sin)); + // TODO allows to use more user friendly type to replace raw buffer usage + retval = sendto(php_sock->bsd_socket, buf, ((size_t)len > buf_len) ? buf_len : (size_t)len, flags, (struct sockaddr *) &sll, sizeof(sll)); break; - */ #endif default: - zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, or AF_INET6"); + zend_argument_value_error(1, "must be one of AF_UNIX, AF_INET, AF_PACKET or AF_INET6"); RETURN_THROWS(); } @@ -2882,8 +3183,6 @@ PHP_FUNCTION(socket_addrinfo_connect) ai = Z_ADDRESS_INFO_P(arg1); - PHP_ETH_PROTO_CHECK(ai->addrinfo.ai_protocol, ai->addrinfo.ai_family); - object_init_ex(return_value, socket_ce); php_sock = Z_SOCKET_P(return_value); diff --git a/ext/sockets/sockets.stub.php b/ext/sockets/sockets.stub.php index d647f46b80dca..3455a82facca3 100644 --- a/ext/sockets/sockets.stub.php +++ b/ext/sockets/sockets.stub.php @@ -2,6 +2,7 @@ /** @generate-class-entries */ +namespace { /** * @var int * @cvalue AF_UNIX @@ -2020,6 +2021,11 @@ * @cvalue ETH_P_ALL */ const ETH_P_ALL = UNKNOWN; +/** + * @var int + * @cvalue ETH_FRAME_LEN + */ +const ETH_FRAME_LEN = UNKNOWN; #endif /** @@ -2163,3 +2169,49 @@ function socket_wsaprotocol_info_import(string $info_id): Socket|false {} function socket_wsaprotocol_info_release(string $info_id): bool {} #endif +} + +namespace Socket { +#ifdef AF_PACKET + class Packet + { + public int $headerSize; + public ?string $rawPacket; + } + + final class EthernetPacket extends Packet + { + public \Socket $socket; + public int $ethProtocol; + public string $srcMac; + public string $dstMac; + public ?Packet $payload; + } + + final class TcpPacket extends Packet + { + public int $srcPort; + public int $dstPort; + } + + final class UdpPacket extends Packet + { + public int $srcPort; + public int $dstPort; + } + + final class Ipv4Packet extends Packet + { + public string $srcAddr; + public string $dstAddr; + public ?Packet $payload; + } + + final class Ipv6Packet extends Packet + { + public string $srcAddr; + public string $dstAddr; + public ?Packet $payload; + } +#endif +} diff --git a/ext/sockets/sockets_arginfo.h b/ext/sockets/sockets_arginfo.h index 45714fb285174..799fdf95d3bfb 100644 --- a/ext/sockets/sockets_arginfo.h +++ b/ext/sockets/sockets_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 42d486d2666d23569e70860e2b1ef203161792b3 */ + * Stub hash: e84ca66755789692a62893312070dcd373e3c229 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_socket_select, 0, 4, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(1, read, IS_ARRAY, 1) @@ -1108,6 +1108,9 @@ static void register_sockets_symbols(int module_number) #if defined(ETH_P_ALL) REGISTER_LONG_CONSTANT("ETH_P_ALL", ETH_P_ALL, CONST_PERSISTENT); #endif +#if defined(ETH_P_ALL) + REGISTER_LONG_CONSTANT("ETH_FRAME_LEN", ETH_FRAME_LEN, CONST_PERSISTENT); +#endif } static zend_class_entry *register_class_Socket(void) @@ -1129,3 +1132,181 @@ static zend_class_entry *register_class_AddressInfo(void) return class_entry; } + +#if defined(AF_PACKET) +static zend_class_entry *register_class_Socket_Packet(void) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Socket", "Packet", NULL); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, 0); + + zval property_headerSize_default_value; + ZVAL_UNDEF(&property_headerSize_default_value); + zend_string *property_headerSize_name = zend_string_init("headerSize", sizeof("headerSize") - 1, 1); + zend_declare_typed_property(class_entry, property_headerSize_name, &property_headerSize_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(property_headerSize_name); + + zval property_rawPacket_default_value; + ZVAL_UNDEF(&property_rawPacket_default_value); + zend_string *property_rawPacket_name = zend_string_init("rawPacket", sizeof("rawPacket") - 1, 1); + zend_declare_typed_property(class_entry, property_rawPacket_name, &property_rawPacket_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + zend_string_release(property_rawPacket_name); + + return class_entry; +} +#endif + +#if defined(AF_PACKET) +static zend_class_entry *register_class_Socket_EthernetPacket(zend_class_entry *class_entry_Socket_Packet) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Socket", "EthernetPacket", NULL); + class_entry = zend_register_internal_class_with_flags(&ce, class_entry_Socket_Packet, ZEND_ACC_FINAL); + + zval property_socket_default_value; + ZVAL_UNDEF(&property_socket_default_value); + zend_string *property_socket_name = zend_string_init("socket", sizeof("socket") - 1, 1); + zend_string *property_socket_class_Socket = zend_string_init("Socket", sizeof("Socket")-1, 1); + zend_declare_typed_property(class_entry, property_socket_name, &property_socket_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_socket_class_Socket, 0, 0)); + zend_string_release(property_socket_name); + + zval property_ethProtocol_default_value; + ZVAL_UNDEF(&property_ethProtocol_default_value); + zend_string *property_ethProtocol_name = zend_string_init("ethProtocol", sizeof("ethProtocol") - 1, 1); + zend_declare_typed_property(class_entry, property_ethProtocol_name, &property_ethProtocol_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(property_ethProtocol_name); + + zval property_srcMac_default_value; + ZVAL_UNDEF(&property_srcMac_default_value); + zend_string *property_srcMac_name = zend_string_init("srcMac", sizeof("srcMac") - 1, 1); + zend_declare_typed_property(class_entry, property_srcMac_name, &property_srcMac_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release(property_srcMac_name); + + zval property_dstMac_default_value; + ZVAL_UNDEF(&property_dstMac_default_value); + zend_string *property_dstMac_name = zend_string_init("dstMac", sizeof("dstMac") - 1, 1); + zend_declare_typed_property(class_entry, property_dstMac_name, &property_dstMac_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release(property_dstMac_name); + + zval property_payload_default_value; + ZVAL_UNDEF(&property_payload_default_value); + zend_string *property_payload_name = zend_string_init("payload", sizeof("payload") - 1, 1); + zend_string *property_payload_class_Socket_Packet = zend_string_init("Socket\\Packet", sizeof("Socket\\Packet")-1, 1); + zend_declare_typed_property(class_entry, property_payload_name, &property_payload_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_payload_class_Socket_Packet, 0, MAY_BE_NULL)); + zend_string_release(property_payload_name); + + return class_entry; +} +#endif + +#if defined(AF_PACKET) +static zend_class_entry *register_class_Socket_TcpPacket(zend_class_entry *class_entry_Socket_Packet) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Socket", "TcpPacket", NULL); + class_entry = zend_register_internal_class_with_flags(&ce, class_entry_Socket_Packet, ZEND_ACC_FINAL); + + zval property_srcPort_default_value; + ZVAL_UNDEF(&property_srcPort_default_value); + zend_string *property_srcPort_name = zend_string_init("srcPort", sizeof("srcPort") - 1, 1); + zend_declare_typed_property(class_entry, property_srcPort_name, &property_srcPort_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(property_srcPort_name); + + zval property_dstPort_default_value; + ZVAL_UNDEF(&property_dstPort_default_value); + zend_string *property_dstPort_name = zend_string_init("dstPort", sizeof("dstPort") - 1, 1); + zend_declare_typed_property(class_entry, property_dstPort_name, &property_dstPort_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(property_dstPort_name); + + return class_entry; +} +#endif + +#if defined(AF_PACKET) +static zend_class_entry *register_class_Socket_UdpPacket(zend_class_entry *class_entry_Socket_Packet) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Socket", "UdpPacket", NULL); + class_entry = zend_register_internal_class_with_flags(&ce, class_entry_Socket_Packet, ZEND_ACC_FINAL); + + zval property_srcPort_default_value; + ZVAL_UNDEF(&property_srcPort_default_value); + zend_string *property_srcPort_name = zend_string_init("srcPort", sizeof("srcPort") - 1, 1); + zend_declare_typed_property(class_entry, property_srcPort_name, &property_srcPort_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(property_srcPort_name); + + zval property_dstPort_default_value; + ZVAL_UNDEF(&property_dstPort_default_value); + zend_string *property_dstPort_name = zend_string_init("dstPort", sizeof("dstPort") - 1, 1); + zend_declare_typed_property(class_entry, property_dstPort_name, &property_dstPort_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(property_dstPort_name); + + return class_entry; +} +#endif + +#if defined(AF_PACKET) +static zend_class_entry *register_class_Socket_Ipv4Packet(zend_class_entry *class_entry_Socket_Packet) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Socket", "Ipv4Packet", NULL); + class_entry = zend_register_internal_class_with_flags(&ce, class_entry_Socket_Packet, ZEND_ACC_FINAL); + + zval property_srcAddr_default_value; + ZVAL_UNDEF(&property_srcAddr_default_value); + zend_string *property_srcAddr_name = zend_string_init("srcAddr", sizeof("srcAddr") - 1, 1); + zend_declare_typed_property(class_entry, property_srcAddr_name, &property_srcAddr_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release(property_srcAddr_name); + + zval property_dstAddr_default_value; + ZVAL_UNDEF(&property_dstAddr_default_value); + zend_string *property_dstAddr_name = zend_string_init("dstAddr", sizeof("dstAddr") - 1, 1); + zend_declare_typed_property(class_entry, property_dstAddr_name, &property_dstAddr_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release(property_dstAddr_name); + + zval property_payload_default_value; + ZVAL_UNDEF(&property_payload_default_value); + zend_string *property_payload_name = zend_string_init("payload", sizeof("payload") - 1, 1); + zend_string *property_payload_class_Socket_Packet = zend_string_init("Socket\\Packet", sizeof("Socket\\Packet")-1, 1); + zend_declare_typed_property(class_entry, property_payload_name, &property_payload_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_payload_class_Socket_Packet, 0, MAY_BE_NULL)); + zend_string_release(property_payload_name); + + return class_entry; +} +#endif + +#if defined(AF_PACKET) +static zend_class_entry *register_class_Socket_Ipv6Packet(zend_class_entry *class_entry_Socket_Packet) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Socket", "Ipv6Packet", NULL); + class_entry = zend_register_internal_class_with_flags(&ce, class_entry_Socket_Packet, ZEND_ACC_FINAL); + + zval property_srcAddr_default_value; + ZVAL_UNDEF(&property_srcAddr_default_value); + zend_string *property_srcAddr_name = zend_string_init("srcAddr", sizeof("srcAddr") - 1, 1); + zend_declare_typed_property(class_entry, property_srcAddr_name, &property_srcAddr_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release(property_srcAddr_name); + + zval property_dstAddr_default_value; + ZVAL_UNDEF(&property_dstAddr_default_value); + zend_string *property_dstAddr_name = zend_string_init("dstAddr", sizeof("dstAddr") - 1, 1); + zend_declare_typed_property(class_entry, property_dstAddr_name, &property_dstAddr_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_string_release(property_dstAddr_name); + + zval property_payload_default_value; + ZVAL_UNDEF(&property_payload_default_value); + zend_string *property_payload_name = zend_string_init("payload", sizeof("payload") - 1, 1); + zend_string *property_payload_class_Socket_Packet = zend_string_init("Socket\\Packet", sizeof("Socket\\Packet")-1, 1); + zend_declare_typed_property(class_entry, property_payload_name, &property_payload_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_payload_class_Socket_Packet, 0, MAY_BE_NULL)); + zend_string_release(property_payload_name); + + return class_entry; +} +#endif diff --git a/ext/sockets/tests/socket_afpacket.phpt b/ext/sockets/tests/socket_afpacket.phpt index 2e83a654766e0..4b10ed10cfad3 100644 --- a/ext/sockets/tests/socket_afpacket.phpt +++ b/ext/sockets/tests/socket_afpacket.phpt @@ -15,7 +15,7 @@ if (!function_exists("posix_getuid") || posix_getuid() != 0) { ?> --FILE-- getMessage(), PHP_EOL; + } + + socket_close($s_c); + + $s_c = socket_create(AF_PACKET, SOCK_RAW, ETH_P_ALL); + $s_bind = socket_bind($s_c, 'lo'); + + $s_s = socket_create(AF_PACKET, SOCK_RAW, ETH_P_IP); + $v_bind = socket_bind($s_s, 'lo'); + + $ip = hex2bin( + "4500" . + "0028" . + "0000" . + "4000" . + "4006" . + "0000" . + "7f000001" . + "7f000001" + ); + $p = str_repeat("A", 20); + + $buf = pack("H12H12n", "ffffffffffff", "000000000000", ETH_P_IP); + $buf .= $ip . $p; + + $min_frame_size = 60; + $buf .= str_repeat("\x00", max(0, $min_frame_size - strlen($buf))); + + var_dump(socket_sendto($s_s, $buf, strlen($buf), 0, "lo", 1)); + var_dump(socket_recvfrom($s_c, $rsp, strlen($buf), 0, $addr)); + + var_dump($addr); + var_dump($rsp); + + socket_close($s_s); + socket_close($s_c); + + $s_c = socket_create(AF_PACKET, SOCK_RAW, ETH_P_IPV6); + $s_bind = socket_bind($s_c, 'lo'); + $s_s = socket_create(AF_PACKET, SOCK_RAW, ETH_P_IPV6); + $v_bind = socket_bind($s_s, 'lo'); + + $ethhdr = pack("H12H12n", "ffffffffffff", "000000000000", ETH_P_IPV6); + + $ipv6_first_4 = hex2bin("60000000"); + + $ipv6_payload_len = hex2bin("0014"); + + $ipv6_nexthdr = chr(6); + + $ipv6_hop_limit = chr(64); + + $src_ip = hex2bin("00000000000000000000000000000001"); + + $dst_ip = hex2bin("00000000000000000000000000000001"); + + $tcp_hdr = pack("nnNNCCnn", + 12345, + 80, + 0, + 0, + (5 << 4), + 0x02, + 65535, + 0 + ); + + $buf = $ethhdr + . $ipv6_first_4 + . $ipv6_payload_len + . $ipv6_nexthdr + . $ipv6_hop_limit + . $src_ip + . $dst_ip + . $tcp_hdr; + + $buf .= str_repeat("\x00", max(0, 60 - strlen($buf))); + + var_dump(socket_sendto($s_s, $buf, strlen($buf), 0, "lo", 1)); + var_dump(socket_recvfrom($s_c, $rsp, strlen($buf), 0, $addr)); + + var_dump($addr); + var_dump($rsp); + + $first_4_bytes = hex2bin("60000000"); + + $payload_len = hex2bin("0014"); + + $nexthdr = hex2bin("43"); // Extended proto + + $hop_limit = hex2bin("40"); + + $src_ip = hex2bin("00000000000000000000000000000001"); + + $dst_ip = hex2bin("00000000000000000000000000000001"); + + $tcp_payload = str_repeat("A", 20); + + $ethhdr = pack("H12H12n", "ffffffffffff", "000000000000", ETH_P_IPV6); + + $buf = $ethhdr . $first_4_bytes . $payload_len . $nexthdr . $hop_limit . $src_ip . $dst_ip . $tcp_payload; + + $buf .= str_repeat("\x00", max(0, 60 - strlen($buf))); + + var_dump(socket_sendto($s_s, $buf, strlen($buf), 0, "lo", 1)); + + try { + socket_recvfrom($s_c, $rsp, strlen($buf), 0, $addr); + } catch (\ValueError $e) { + echo $e->getMessage(), PHP_EOL; + } ?> --EXPECTF-- bool(true) @@ -35,3 +171,107 @@ string(2) "lo" int(%i) Warning: socket_getpeername(): unable to retrieve peer name [95]: %sot supported in %s on line %d +int(60) +int(60) +string(2) "lo" +object(Socket\EthernetPacket)#3 (%d) { + ["headerSize"]=> + int(%d) + ["rawPacket"]=> + string(%d) "%A" + ["socket"]=> + object(Socket)#1 (0) { + } + ["ethProtocol"]=> + int(%i) + ["srcMac"]=> + string(%d) "%s:%s:%s:%s:%s:%s" + ["dstMac"]=> + string(%d) "%s:%s:%s:%s:%s:%s" + ["payload"]=> + %a +} +int(60) +unsupported ethernet protocol +int(60) +int(%d) +string(2) "lo" +object(Socket\EthernetPacket)#2 (7) { + ["headerSize"]=> + int(14) + ["rawPacket"]=> + string(%d) "%A" + ["socket"]=> + object(Socket)#%d (0) { + } + ["ethProtocol"]=> + int(2048) + ["srcMac"]=> + string(%d) "%s:%s:%s:%s:%s:%s" + ["dstMac"]=> + string(%d) "%s:%s:%s:%s:%s:%s" + ["payload"]=> + object(Socket\Ipv4Packet)#%d (5) { + ["headerSize"]=> + int(%d) + ["rawPacket"]=> + string(%d) "%s" + ["srcAddr"]=> + string(%d) "%d.%d.%d.%d" + ["dstAddr"]=> + string(%d) "%d.%d.%d.%d" + ["payload"]=> + object(Socket\%sPacket)#%d (4) { + ["headerSize"]=> + int(%d) + ["rawPacket"]=> + string(%d) "%s" + ["srcPort"]=> + int(%d) + ["dstPort"]=> + int(%d) + } + } +} +int(%d) +int(%d) +string(2) "lo" +object(Socket\EthernetPacket)#%d (7) { + ["headerSize"]=> + int(14) + ["rawPacket"]=> + string(%d) "%A" + ["socket"]=> + object(Socket)#3 (0) { + } + ["ethProtocol"]=> + int(%d) + ["srcMac"]=> + string(%d) "%s:%s:%s:%s:%s:%s" + ["dstMac"]=> + string(%d) "%s:%s:%s:%s:%s:%s" + ["payload"]=> + object(Socket\Ipv6Packet)#%d (5) { + ["headerSize"]=> + int(%d) + ["rawPacket"]=> + string(%d) "%A" + ["srcAddr"]=> + string(%d) "%s" + ["dstAddr"]=> + string(%d) "%s" + ["payload"]=> + object(Socket\TcpPacket)#%d (4) { + ["headerSize"]=> + int(%d) + ["rawPacket"]=> + string(%d) "%A" + ["srcPort"]=> + int(%d) + ["dstPort"]=> + int(%d) + } + } +} +int(%d) +unsupported ip header protocol