diff --git a/meson.build b/meson.build index bdd827f5..742d6f09 100644 --- a/meson.build +++ b/meson.build @@ -288,6 +288,7 @@ test_files = [ 'test/suite-io.janet', 'test/suite-marsh.janet', 'test/suite-math.janet', + 'test/suite-net.janet', 'test/suite-os.janet', 'test/suite-parse.janet', 'test/suite-peg.janet', diff --git a/src/core/net.c b/src/core/net.c index c3181519..a9bcb0f9 100644 --- a/src/core/net.c +++ b/src/core/net.c @@ -120,6 +120,25 @@ static void janet_net_socknoblock(JSock s) { #endif } +/* Allow specifying IPV6 vs. IPV4 (or unix domain socket) */ +static int net_get_address_family(Janet x) { + if (janet_checktype(x, JANET_NIL)) { + return AF_UNSPEC; + } + if (janet_keyeq(x, "ipv4")) { + return AF_INET; + } + if (janet_keyeq(x, "ipv6")) { + return AF_INET6; + } +#ifndef JANET_WINDOWS + if (janet_keyeq(x, "unix")) { + return AF_UNIX; + } +#endif + return AF_UNSPEC; +} + /* State machine for async connect */ void net_callback_connect(JanetFiber *fiber, JanetAsyncEvent event) { @@ -596,10 +615,11 @@ JANET_CORE_FN(cfun_net_connect, } JANET_CORE_FN(cfun_net_socket, - "(net/socket &opt type)", + "(net/socket &opt type address-family)", "Creates a new unbound socket. Type is an optional keyword, " - "either a :stream (usually tcp), or :datagram (usually udp). The default is :stream.") { - janet_arity(argc, 0, 1); + "either a :stream (usually tcp), or :datagram (usually udp). The default is :stream. " + "`address-family` should be one of :ipv4 or :ipv6.") { + janet_arity(argc, 0, 2); int socktype = janet_get_sockettype(argv, argc, 0); @@ -610,7 +630,14 @@ JANET_CORE_FN(cfun_net_socket, memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = socktype; +#ifdef AI_NUMERICSERV + hints.ai_flags = AI_NUMERICSERV; /* Explicitly prevent name resolution */ +#else hints.ai_flags = 0; +#endif + if (argc >= 2) { + hints.ai_family = net_get_address_family(argv[1]); + } int status = getaddrinfo(NULL, "0", &hints, &ai); if (status) { janet_panicf("could not get address info: %s", gai_strerror(status)); @@ -1038,6 +1065,8 @@ static const struct sockopt_type sockopt_type_list[] = { #ifndef JANET_NO_IPV6 { "ipv6-join-group", IPPROTO_IPV6, IPV6_JOIN_GROUP, JANET_POINTER }, { "ipv6-leave-group", IPPROTO_IPV6, IPV6_LEAVE_GROUP, JANET_POINTER }, + { "ipv6-multicast-hops", IPPROTO_IPV6, IPV6_MULTICAST_HOPS, JANET_NUMBER }, + { "ipv6-unicast-hops", IPPROTO_IPV6, IPV6_UNICAST_HOPS, JANET_NUMBER }, #endif { NULL, 0, 0, JANET_POINTER } }; @@ -1054,7 +1083,10 @@ JANET_CORE_FN(cfun_net_setsockopt, "- :ip-add-membership string\n" "- :ip-drop-membership string\n" "- :ipv6-join-group string\n" - "- :ipv6-leave-group string\n") { + "- :ipv6-leave-group string\n" + "- :ipv6-multicast-hops number\n" + "- :ipv6-unicast-hops number\n" + ) { janet_arity(argc, 3, 3); JanetStream *stream = janet_getabstract(argv, 0, &janet_stream_type); janet_stream_flags(stream, JANET_STREAM_SOCKET); @@ -1073,9 +1105,7 @@ JANET_CORE_FN(cfun_net_setsockopt, } union { -#ifdef JANET_BSD - u_char v_uchar; -#endif + unsigned char v_uchar; int v_int; struct ip_mreq v_mreq; #ifndef JANET_NO_IPV6 diff --git a/test/helper.janet b/test/helper.janet index c0b89b78..293b4ccc 100644 --- a/test/helper.janet +++ b/test/helper.janet @@ -1,7 +1,7 @@ # Helper code for running tests # Turn on strict linting by default in test suite. -(put root-env *lint-warn* :strict) +#(put root-env *lint-warn* :strict) (var num-tests-passed 0) (var num-tests-run 0) @@ -67,8 +67,8 @@ (def e (gensym)) (def f (gensym)) (if is-verbose - ~(try (do ,;forms (,assert true ,msg)) ([,e ,f] (,assert false ,msg) (,debug/stacktrace ,f ,e "\e[31m✘\e[0m "))) - ~(try (do ,;forms (,assert true ,msg)) ([_] (,assert false ,msg))))) + ~(try (do ,;forms (as-macro ,assert true ,msg)) ([,e ,f] (as-macro ,assert false ,msg) (,debug/stacktrace ,f ,e "\e[31m✘\e[0m "))) + ~(try (do ,;forms (as-macro ,assert true ,msg)) ([_] (as-macro ,assert false ,msg))))) (defn start-suite [&opt x] (default x (dyn :current-file)) diff --git a/test/suite-net.janet b/test/suite-net.janet new file mode 100644 index 00000000..1cf59fab --- /dev/null +++ b/test/suite-net.janet @@ -0,0 +1,35 @@ +# Copyright (c) 2026 Calvin Rose & contributors +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +# Expand on ev testing with some extra network protocol testing. + +(import ./helper :prefix "" :exit true) +(start-suite) + +# Smoke +(assert true) + +# Raw socket testing +(def s (net/socket :datagram :ipv4)) +(assert-no-error "multicast ipv4" (net/setsockopt s :ip-multicast-ttl 255)) +(def s6 (net/socket :datagram :ipv6)) +(assert-no-error "multicast ipv6" (net/setsockopt s6 :ipv6-multicast-hops 255)) + +(end-suite)