# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License, Version 1.0 only # (the "License"). You may not use this file except in compliance # with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2004 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # LIBRARY = libsock.a OBJECTS = socket.o sock_test.o include ../Makefile.com LDLIBS += -linet -ltcp CPPFLAGS += -I../inet -I../tcp $(SOCKCPPFLAGS) CERRWARN += -Wno-type-limits include ../Makefile.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2001-2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * sock_test.c. Implementing a CLI for inetboot testing. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include "socket_impl.h" #include "socket_inet.h" #include #include #include #include #include #include #include #include #include "tcp_inet.h" #include "ipv4.h" #include static int atoi(const char *); static int st_accept(void); static int st_bind(void); static int st_connect(void); static int st_echo(void); static int st_getsockname(void); static int st_getsockopt(void); static int st_get_addr_and_port(in_addr_t *, unsigned short *); static int st_get_buf_and_cnt(char **, int *); static int st_listen(void); static int st_match_option(char *, int *, int *); static int st_send(void); static int st_sendto(void); static int st_recv(void); static int st_recvfrom(void); static int st_set_addr(void); static int st_set_netmask(void); static int st_set_router(void); static int st_setsockopt(void); static int st_socket(void); static int st_sock_close(void); static int st_tcp_tw_report(void); static int st_toggle_promiscuous(void); static int st_use_obp(void); /* Wrapper for socket calls. */ static int st_local_accept(int, struct sockaddr *, socklen_t *); static int st_local_bind(int, const struct sockaddr *, socklen_t); static int st_local_connect(int, const struct sockaddr *, socklen_t); static int st_local_getsockname(int, struct sockaddr *, socklen_t *); static int st_local_getsockopt(int, int, int, void *, socklen_t *); static int st_local_listen(int, int); static int st_local_recv(int, void *, size_t, int); static int st_local_recvfrom(int, void *, size_t, int, struct sockaddr *, socklen_t *); static int st_local_send(int, const void *, size_t, int); static int st_local_sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t); static int st_local_setsockopt(int, int, int, const void *, socklen_t); static int st_local_socket(int, int, int); static int st_local_socket_close(int); struct sock_test_cmd_s { char *st_cmd; int (*st_fn)(void); }; static struct sock_test_cmd_s st_cmds[] = { { "set_addr", st_set_addr}, { "set_netmask", st_set_netmask}, { "set_router", st_set_router}, { "socket", st_socket }, { "bind", st_bind }, { "accept", st_accept }, { "connect", st_connect }, { "listen", st_listen }, { "send", st_send }, { "sendto", st_sendto }, { "recv", st_recv }, { "recvfrom", st_recvfrom }, { "setsockopt", st_setsockopt }, { "getsockopt", st_getsockopt }, { "getsockname", st_getsockname }, { "close", st_sock_close }, { "echo", st_echo }, { "toggle_promiscous", st_toggle_promiscuous}, { "use_obp", st_use_obp}, { "tcp_tw_report", st_tcp_tw_report}, { NULL, NULL } }; struct so_option_string_s { char *so_name; int so_opt; int so_opt_level; } so_option_array[] = { { "rcvtimeo", SO_RCVTIMEO, SOL_SOCKET }, { "dontroute", SO_DONTROUTE, SOL_SOCKET }, { "reuseaddr", SO_REUSEADDR, SOL_SOCKET }, { "rcvbuf", SO_RCVBUF, SOL_SOCKET }, { "sndbuf", SO_SNDBUF, SOL_SOCKET }, { NULL, 0 } }; #define NO_OPENED_SOCKET -1 /* Right now, we only allow one socket at one time. */ static int g_sock_fd = NO_OPENED_SOCKET; static int save_g_sock_fd = NO_OPENED_SOCKET; /* Boolean to decide if OBP network routines should be used. */ static boolean_t use_obp = B_FALSE; /* * The following routines are wrappers for the real socket routines. The * boolean use_obp is used to decide whether the real socket routines is * called or the "equivalent" OBP provided routines should be called. */ static int st_local_socket(int domain, int type, int protocol) { if (!use_obp) { return (socket(domain, type, protocol)); } else { return (0); } } static int st_local_socket_close(int sd) { if (!use_obp) { return (socket_close(sd)); } else { return (0); } } static int st_local_accept(int sd, struct sockaddr *addr, socklen_t *addr_len) { if (!use_obp) { return (accept(sd, addr, addr_len)); } else { return (0); } } static int st_local_bind(int sd, const struct sockaddr *name, socklen_t namelen) { if (!use_obp) { return (bind(sd, name, namelen)); } else { return (0); } } static int st_local_connect(int sd, const struct sockaddr *addr, socklen_t addr_len) { if (!use_obp) { return (connect(sd, addr, addr_len)); } else { return (0); } } static int st_local_listen(int sd, int backlog) { if (!use_obp) { return (listen(sd, backlog)); } else { return (0); } } static int st_local_send(int sd, const void *msg, size_t len, int flags) { if (!use_obp) { return (send(sd, msg, len, flags)); } else { return (0); } } static int st_local_sendto(int sd, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) { if (!use_obp) { return (sendto(sd, msg, len, flags, to, tolen)); } else { return (0); } } static int st_local_recv(int sd, void *buf, size_t len, int flags) { if (!use_obp) { return (recv(sd, buf, len, flags)); } else { return (0); } } static int st_local_recvfrom(int sd, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen) { if (!use_obp) { return (recvfrom(sd, buf, len, flags, from, fromlen)); } else { return (0); } } static int st_local_getsockname(int sd, struct sockaddr *name, socklen_t *namelen) { if (!use_obp) { return (getsockname(sd, name, namelen)); } else { return (0); } } static int st_local_getsockopt(int sd, int level, int option, void *optval, socklen_t *optlen) { if (!use_obp) { return (getsockopt(sd, level, option, optval, optlen)); } else { return (0); } } static int st_local_setsockopt(int sd, int level, int option, const void *optval, socklen_t optlen) { if (!use_obp) { return (setsockopt(sd, level, option, optval, optlen)); } else { return (0); } } static int atoi(const char *p) { int n; int c = *p++, neg = 0; while (isspace(c)) { c = *p++; } if (!isdigit(c)) { switch (c) { case '-': neg++; /* FALLTHROUGH */ case '+': c = *p++; } } for (n = 0; isdigit(c); c = *p++) { n *= 10; /* two steps to avoid unnecessary overflow */ n += '0' - c; /* accum neg to avoid surprises at MAX */ } return (neg ? n : -n); } int st_interpret(char *buf) { char *cmd; int i; if ((cmd = strtok(buf, " ")) == NULL) return (-1); for (i = 0; st_cmds[i].st_cmd != NULL; i++) { if (strcmp(cmd, st_cmds[i].st_cmd) == 0) { return (st_cmds[i].st_fn()); } } printf("! Unknown command: %s\n", cmd); return (-1); } static int st_socket(void) { char *type; if ((type = strtok(NULL, " ")) == NULL) { printf("! usage: socket type\n"); return (-1); } if (g_sock_fd != NO_OPENED_SOCKET) { printf("! Cannot open more than 1 socket\n"); return (-1); } if (strcmp(type, "stream") == 0) { if ((g_sock_fd = st_local_socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("! Error in opening TCP socket: %d\n", errno); return (-1); } else { printf("@ TCP socket opened\n"); } } else if (strcmp(type, "dgram") == 0) { if ((g_sock_fd = st_local_socket(AF_INET, SOCK_DGRAM, 0)) < 0) { printf("! Error in opening UDP socket: %d\n", errno); return (-1); } else { printf("@ UDP socket opened\n"); } } else if (strcmp(type, "raw") == 0) { if ((g_sock_fd = st_local_socket(AF_INET, SOCK_RAW, 0)) < 0) { printf("! Error in opening RAW socket: %d\n", errno); return (-1); } else { printf("@ RAW socket opened\n"); } } else { printf("! Unknown socket type: %s\n", type); return (-1); } return (0); } static int st_set_addr(void) { char *tmp; struct in_addr addr; tmp = strtok(NULL, " "); if (tmp == NULL) { printf("! No address given\n"); return (-1); } if ((addr.s_addr = inet_addr(tmp)) == (uint32_t)-1) { printf("! Malformed address\n"); return (-1); } ipv4_setipaddr(&addr); printf("@ IP address %s set\n", inet_ntoa(addr)); return (0); } static int st_set_netmask(void) { char *tmp; struct in_addr addr; tmp = strtok(NULL, " "); if (tmp == NULL) { printf("! No netmask given\n"); return (-1); } if ((addr.s_addr = inet_addr(tmp)) == (uint32_t)-1) { printf("! Malformed netmask\n"); return (-1); } ipv4_setnetmask(&addr); printf("@ Netmask %s set\n", inet_ntoa(addr)); return (0); } static int st_set_router(void) { char *tmp; struct in_addr addr; tmp = strtok(NULL, " "); if (tmp == NULL) { printf("! No router address given\n"); return (-1); } if ((addr.s_addr = inet_addr(tmp)) == (uint32_t)-1) { printf("! Malformed router address\n"); return (-1); } ipv4_setdefaultrouter(&addr); if (ipv4_route(IPV4_ADD_ROUTE, RT_DEFAULT, NULL, &addr) < 0) { printf("! Cannot add default route\n"); } else { printf("@ Default router %s set\n", inet_ntoa(addr)); } return (0); } static int st_get_addr_and_port(in_addr_t *addr, unsigned short *port) { char *tmp; if (g_sock_fd == NO_OPENED_SOCKET) { printf("! No socket opened\n"); return (-1); } tmp = strtok(NULL, "/"); if (tmp == NULL) { printf("! No address given\n"); return (-1); } if ((*addr = inet_addr(tmp)) == (uint32_t)-1) { printf("! Malformed address\n"); return (-1); } tmp = strtok(NULL, " "); if (tmp == NULL) { printf("! No port given\n"); return (-1); } *port = htons(atoi(tmp)); return (0); } static int st_bind(void) { struct sockaddr_in local_addr; if (st_get_addr_and_port(&(local_addr.sin_addr.s_addr), &(local_addr.sin_port)) < 0) { return (-1); } local_addr.sin_family = AF_INET; if (st_local_bind(g_sock_fd, (struct sockaddr *)&local_addr, sizeof (local_addr)) < 0) { printf("! Bind failed: %d\n", errno); return (-1); } printf("@ Socket bound to %s/%d\n", inet_ntoa(local_addr.sin_addr), ntohs(local_addr.sin_port)); return (0); } static int st_listen(void) { char *tmp; if (g_sock_fd == NO_OPENED_SOCKET) { printf("! No socket opened\n"); return (-1); } if ((tmp = strtok(NULL, " ")) == NULL) { printf("! No backlog given\n"); return (-1); } if (st_local_listen(g_sock_fd, atoi(tmp)) < 0) { printf("! Listen failed: %d\n", errno); return (-1); } printf("@ Listen succeeded\n"); return (0); } static int st_accept(void) { struct sockaddr_in addr; socklen_t addr_len; int sd; if (g_sock_fd == NO_OPENED_SOCKET) { printf("! No socket opened\n"); return (-1); } addr_len = sizeof (struct sockaddr_in); if ((sd = st_local_accept(g_sock_fd, (struct sockaddr *)&addr, &addr_len)) < 0) { printf("! Accept failed: %d\n", errno); return (-1); } printf("@ Accept succeeded from %s:%d. Socket descriptor saved\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); save_g_sock_fd = g_sock_fd; g_sock_fd = sd; return (0); } static int st_connect(void) { struct sockaddr_in peer_addr; if (st_get_addr_and_port(&(peer_addr.sin_addr.s_addr), &(peer_addr.sin_port)) < 0) { return (-1); } peer_addr.sin_family = AF_INET; if (st_local_connect(g_sock_fd, (struct sockaddr *)&peer_addr, sizeof (peer_addr)) < 0) { printf("! Connect failed: %d\n", errno); return (-1); } printf("@ Socket connected to %s/%d\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port)); return (0); } static int st_get_buf_and_cnt(char **buf, int *send_cnt) { char *cnt; if ((*buf = strtok(NULL, " ")) == NULL) { printf("! No send buffer\n"); return (-1); } if ((cnt = strtok(NULL, " ")) == NULL) { printf("! Missing send length\n"); return (-1); } if ((*send_cnt = atoi(cnt)) < 0) { printf("! Invalid send count\n"); return (-1); } return (0); } static int st_send(void) { char *buf; int send_cnt; if (g_sock_fd == NO_OPENED_SOCKET) { printf("! No socket opened\n"); return (-1); } if (st_get_buf_and_cnt(&buf, &send_cnt) < 0) return (-1); if ((send_cnt = st_local_send(g_sock_fd, buf, send_cnt, 0)) < 0) { printf("! Send failed: %d\n", errno); return (-1); } printf("@ Send %d bytes\n", send_cnt); return (0); } static int st_sendto(void) { struct sockaddr_in peer_addr; char *buf; int send_cnt; if (st_get_addr_and_port(&(peer_addr.sin_addr.s_addr), &(peer_addr.sin_port)) < 0) { return (-1); } peer_addr.sin_family = AF_INET; if (st_get_buf_and_cnt(&buf, &send_cnt) < 0) return (-1); if ((send_cnt = st_local_sendto(g_sock_fd, buf, send_cnt, 0, (struct sockaddr *)&peer_addr, sizeof (peer_addr))) < 0) { printf("! Sendto failed: %d\n", errno); return (-1); } printf("@ Send %d bytes\n", send_cnt); return (0); } static int st_recv(void) { char *tmp; char *buf; int buf_len, ret; if (g_sock_fd == NO_OPENED_SOCKET) { printf("! No socket opened\n"); return (-1); } if ((tmp = strtok(NULL, " ")) == NULL) { printf("! No buffer len given\n"); return (-1); } buf_len = atoi(tmp); if ((buf = bkmem_zalloc(buf_len)) == NULL) { printf("! Cannot allocate buffer: %d\n", errno); return (-1); } if ((ret = st_local_recv(g_sock_fd, buf, buf_len, 0)) <= 0) { if (ret == 0) { printf("@ EOF received: %d\n", errno); return (0); } printf("! Cannot recv: %d\n", errno); return (-1); } printf("@ Bytes received: %d\n", ret); hexdump(buf, ret); bkmem_free(buf, buf_len); return (0); } static int st_recvfrom(void) { char *tmp; char *buf; int buf_len, ret; struct sockaddr_in from; socklen_t fromlen; if (g_sock_fd == NO_OPENED_SOCKET) { printf("! No socket opened\n"); return (-1); } if ((tmp = strtok(NULL, " ")) == NULL) { printf("! No buffer len given\n"); return (-1); } buf_len = atoi(tmp); if ((buf = bkmem_zalloc(buf_len)) == NULL) { printf("! Cannot allocate buffer: %d\n", errno); return (-1); } fromlen = sizeof (from); if ((ret = st_local_recvfrom(g_sock_fd, buf, buf_len, 0, (struct sockaddr *)&from, &fromlen)) <= 0) { if (ret == 0) { printf("@ EOF received: %d\n", errno); return (0); } printf("! Cannot recv: %d\n", errno); return (-1); } printf("@ Bytes received from %s/%d: %d\n", inet_ntoa(from.sin_addr), ntohs(from.sin_port), ret); hexdump(buf, ret); bkmem_free(buf, buf_len); return (0); } /* * To act as an echo server. Note that it assumes the address and * netmask have been set. */ static int st_echo(void) { char *tmp; int listen_fd, newfd; int echo_port; struct sockaddr_in addr; socklen_t addr_size; int backlog = 20; char *buf; int buf_len, ret, snd_cnt; tmp = strtok(NULL, " "); if (tmp == NULL) { printf("! No echo port given\n"); return (-1); } echo_port = atoi(tmp); tmp = strtok(NULL, " "); if (tmp == NULL) { printf("! No buffer size given\n"); return (-1); } buf_len = atoi(tmp); /* Create local socket for echo server */ if ((listen_fd = st_local_socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("! Error in opening TCP socket: %d\n", errno); return (-1); } else { printf("@ Local TCP socket opened\n"); } /* Bind local socket */ addr.sin_family = AF_INET; addr.sin_port = htons(echo_port); addr.sin_addr.s_addr = INADDR_ANY; if (st_local_bind(listen_fd, (struct sockaddr *)&addr, sizeof (addr)) < 0) { printf("! Bind failed: %d\n", errno); return (-1); } if (st_local_listen(listen_fd, backlog) < 0) { printf("! Listen failed: %d\n", errno); return (-1); } addr_size = sizeof (addr); if ((newfd = st_local_accept(listen_fd, (struct sockaddr *)&addr, &addr_size)) < 0) { printf("! Accept failed: %d\n", errno); (void) st_local_socket_close(listen_fd); return (-1); } printf("@ Accepted connection: %s/%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); (void) st_local_socket_close(listen_fd); if ((buf = bkmem_zalloc(buf_len)) == NULL) { printf("! Cannot allocate buffer: %d\n", errno); (void) st_local_socket_close(newfd); return (-1); } while ((ret = st_local_recv(newfd, buf, buf_len, 0)) > 0) { printf("@ Bytes received: %d\n", ret); hexdump(buf, ret); if ((snd_cnt = st_local_send(newfd, buf, ret, 0)) < ret) { printf("! Send failed: %d\n", errno); bkmem_free(buf, buf_len); return (-1); } printf("@ Sent %d bytes\n", snd_cnt); } (void) st_local_socket_close(newfd); if (ret < 0) { printf("! Cannot recv: %d\n", errno); bkmem_free(buf, buf_len); return (-1); } else { return (0); } } static int st_match_option(char *opt_s, int *opt, int *opt_level) { int i; for (i = 0; so_option_array[i].so_name != NULL; i++) { if (strcmp(so_option_array[i].so_name, opt_s) == 0) { *opt = so_option_array[i].so_opt; *opt_level = so_option_array[i].so_opt_level; return (0); } } printf("! Unknown option\n"); return (-1); } static int st_setsockopt(void) { char *tmp; int opt, opt_level, opt_val; if (g_sock_fd == NO_OPENED_SOCKET) { printf("! No socket opened\n"); return (-1); } if ((tmp = strtok(NULL, " ")) == NULL) { printf("! No option given\n"); return (-1); } if (st_match_option(tmp, &opt, &opt_level) < 0) { return (-1); } /* We only support integer option for the moment. */ if ((tmp = strtok(NULL, " ")) == NULL) { printf("! No option value given\n"); return (-1); } opt_val = atoi(tmp); if (st_local_setsockopt(g_sock_fd, opt_level, opt, &opt_val, sizeof (int)) < 0) { printf("! Cannot set option: %d\n", errno); return (-1); } printf("@ Option set successfully\n"); return (0); } static int st_getsockname(void) { struct sockaddr_in addr; socklen_t len; if (g_sock_fd == NO_OPENED_SOCKET) { printf("! No socket opened\n"); return (-1); } len = sizeof (addr); if (st_local_getsockname(g_sock_fd, (struct sockaddr *)&addr, &len) < 0) { printf("! getsockname failed: %d\n", errno); return (-1); } printf("@ Local socket name: %s/%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); return (0); } static int st_getsockopt(void) { char *tmp; int opt, opt_level, opt_val; socklen_t opt_len; if (g_sock_fd == NO_OPENED_SOCKET) { printf("! No socket opened\n"); return (-1); } if ((tmp = strtok(NULL, " ")) == NULL) { printf("! No option given\n"); return (-1); } if (st_match_option(tmp, &opt, &opt_level) < 0) { return (-1); } opt_len = sizeof (opt_val); if (st_local_getsockopt(g_sock_fd, opt_level, opt, &opt_val, &opt_len) < 0) { printf("! Cannot get option: %d\n", errno); return (-1); } printf("@ Option value is %d\n", opt_val); return (-1); } static int st_sock_close(void) { if (g_sock_fd == NO_OPENED_SOCKET) { printf("! No socket opened\n"); return (-1); } if (st_local_socket_close(g_sock_fd) < 0) { printf("! Error in closing socket: %d\n", errno); return (-1); } printf("@ Socket closed"); if (save_g_sock_fd != NO_OPENED_SOCKET) { g_sock_fd = save_g_sock_fd; save_g_sock_fd = NO_OPENED_SOCKET; printf(", switching to saved socket descriptor\n"); } else { g_sock_fd = NO_OPENED_SOCKET; printf("\n"); } return (0); } static int st_toggle_promiscuous(void) { /* We always start with non-promiscuous mode. */ static boolean_t promiscuous = B_FALSE; promiscuous = !promiscuous; (void) ipv4_setpromiscuous(promiscuous); printf("@ Setting promiscuous to %d\n", promiscuous); return (0); } static int st_use_obp(void) { if ((use_obp = !use_obp) == B_TRUE) { printf("@ Now using OBP routines\n"); } else { printf("@ Now using socket routines\n"); } return (0); } static int st_tcp_tw_report(void) { printf("@ TCP Time Wait report\n"); tcp_time_wait_report(); return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2001-2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Socket CLI testing header file. */ #ifndef _SOCK_TEST_H #define _SOCK_TEST_H #pragma ident "%Z%%M% %I% %E% SMI" #ifdef __cplusplus extern "C" { #endif extern int st_interpret(char *); #ifdef __cplusplus } #endif #endif /* _SOCK_TEST_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * socket.c, Code implementing a simple socket interface. */ #include #include "socket_impl.h" #include #include #include #include #include #include #include #include #include #include "socket_inet.h" #include "ipv4.h" #include "ipv4_impl.h" #include "udp_inet.h" #include "tcp_inet.h" #include "mac.h" #include "mac_impl.h" #include struct inetboot_socket sockets[MAXSOCKET] = { 0 }; /* Default send and receive socket buffer size */ #define SO_DEF_SNDBUF 48*1024 #define SO_DEF_RCVBUF 48*1024 /* Default max socket buffer size */ #define SO_MAX_BUF 4*1024*1024 static ssize_t dgram_sendto(int, const void *, size_t, int, const struct sockaddr *, int); static ssize_t stream_sendto(int, const void *, size_t, int); static int bind_check(int, const struct sockaddr *); static int quickbind(int); /* Check the validity of a fd and return the socket index of that fd. */ int so_check_fd(int fd, int *errno) { int i; i = FD_TO_SOCKET(fd); if (i < 0 || i >= MAXSOCKET) { *errno = ENOTSOCK; return (-1); } if (sockets[i].type == INETBOOT_UNUSED) { *errno = ENOTSOCK; return (-1); } return (i); } /* * Create an endpoint for network communication. Returns a descriptor. * * Notes: * Only PF_INET communication domains are supported. Within * this domain, only SOCK_RAW, SOCK_DGRAM and SOCK_STREAM types are * supported. */ int socket(int domain, int type, int protocol) { static int sock_initialized; int i; errno = 0; if (!sock_initialized) { for (i = 0; i < MAXSOCKET; i++) sockets[i].type = INETBOOT_UNUSED; sock_initialized = B_TRUE; } if (domain != AF_INET) { errno = EPROTONOSUPPORT; return (-1); } /* Find available socket */ for (i = 0; i < MAXSOCKET; i++) { if (sockets[i].type == INETBOOT_UNUSED) break; } if (i >= MAXSOCKET) { errno = EMFILE; /* No slots left. */ return (-1); } /* Some socket initialization... */ sockets[i].so_rcvbuf = SO_DEF_RCVBUF; sockets[i].so_sndbuf = SO_DEF_SNDBUF; /* * Note that we ignore the protocol field for SOCK_DGRAM and * SOCK_STREAM. When we support different protocols in future, * this needs to be changed. */ switch (type) { case SOCK_RAW: ipv4_raw_socket(&sockets[i], (uint8_t)protocol); break; case SOCK_DGRAM: udp_socket_init(&sockets[i]); break; case SOCK_STREAM: tcp_socket_init(&sockets[i]); break; default: errno = EPROTOTYPE; break; } if (errno != 0) return (-1); /* IPv4 generic initialization. */ ipv4_socket_init(&sockets[i]); /* MAC generic initialization. */ mac_socket_init(&sockets[i]); return (i + SOCKETTYPE); } int getsockname(int s, struct sockaddr *name, socklen_t *namelen) { int i; errno = 0; if ((i = so_check_fd(s, &errno)) == -1) return (-1); if (*namelen < sizeof (struct sockaddr_in)) { errno = ENOMEM; return (-1); } /* Structure assignment... */ *((struct sockaddr_in *)name) = sockets[i].bind; *namelen = sizeof (struct sockaddr_in); return (0); } /* * The socket options we support are: * SO_RCVTIMEO - Value is in msecs, and is of uint32_t. * SO_DONTROUTE - Value is an int, and is a boolean (nonzero if set). * SO_REUSEADDR - Value is an int boolean. * SO_RCVBUF - Value is an int. * SO_SNDBUF - Value is an int. */ int getsockopt(int s, int level, int option, void *optval, socklen_t *optlen) { int i; errno = 0; if ((i = so_check_fd(s, &errno)) == -1) return (-1); switch (level) { case SOL_SOCKET: { switch (option) { case SO_RCVTIMEO: if (*optlen == sizeof (uint32_t)) { *(uint32_t *)optval = sockets[i].in_timeout; } else { *optlen = 0; errno = EINVAL; } break; case SO_DONTROUTE: if (*optlen == sizeof (int)) { *(int *)optval = (sockets[i].out_flags & SO_DONTROUTE); } else { *optlen = 0; errno = EINVAL; } break; case SO_REUSEADDR: if (*optlen == sizeof (int)) { *(int *)optval = (sockets[i].so_opt & SO_REUSEADDR); } else { *optlen = 0; errno = EINVAL; } break; case SO_RCVBUF: if (*optlen == sizeof (int)) { *(int *)optval = sockets[i].so_rcvbuf; } else { *optlen = 0; errno = EINVAL; } break; case SO_SNDBUF: if (*optlen == sizeof (int)) { *(int *)optval = sockets[i].so_sndbuf; } else { *optlen = 0; errno = EINVAL; } break; case SO_LINGER: if (*optlen == sizeof (struct linger)) { /* struct copy */ *(struct linger *)optval = sockets[i].so_linger; } else { *optlen = 0; errno = EINVAL; } break; default: errno = ENOPROTOOPT; break; } break; } /* case SOL_SOCKET */ case IPPROTO_TCP: case IPPROTO_IP: { switch (option) { default: *optlen = 0; errno = ENOPROTOOPT; break; } break; } /* case IPPROTO_IP or IPPROTO_TCP */ default: errno = ENOPROTOOPT; break; } /* switch (level) */ if (errno != 0) return (-1); else return (0); } /* * Generate a network-order source port from the privileged range if * "reserved" is true, dynamic/private range otherwise. We consider the * range of 512-1023 privileged ports as ports we can use. This mirrors * historical rpc client practice for privileged port selection. */ in_port_t get_source_port(boolean_t reserved) { static in_port_t dynamic = IPPORT_DYNAMIC_START - 1, rsvdport = (IPPORT_RESERVED / 2) - 1; in_port_t p; if (reserved) { if (++rsvdport >= IPPORT_RESERVED) p = rsvdport = IPPORT_RESERVED / 2; else p = rsvdport; } else p = ++dynamic; return (htons(p)); } /* * The socket options we support are: * SO_RECVTIMEO - Value is uint32_t msecs. * SO_DONTROUTE - Value is int boolean (nonzero == TRUE, zero == FALSE). * SO_REUSEADDR - value is int boolean. * SO_RCVBUF - Value is int. * SO_SNDBUF - Value is int. */ int setsockopt(int s, int level, int option, const void *optval, socklen_t optlen) { int i; errno = 0; if ((i = so_check_fd(s, &errno)) == -1) return (-1); switch (level) { case SOL_SOCKET: { switch (option) { case SO_RCVTIMEO: if (optlen == sizeof (uint32_t)) sockets[i].in_timeout = *(uint32_t *)optval; else { errno = EINVAL; } break; case SO_DONTROUTE: if (optlen == sizeof (int)) { if (*(int *)optval) sockets[i].out_flags |= SO_DONTROUTE; else sockets[i].out_flags &= ~SO_DONTROUTE; } else { errno = EINVAL; } break; case SO_REUSEADDR: if (optlen == sizeof (int)) { if (*(int *)optval) sockets[i].so_opt |= SO_REUSEADDR; else sockets[i].so_opt &= ~SO_REUSEADDR; } else { errno = EINVAL; } break; case SO_RCVBUF: if (optlen == sizeof (int)) { sockets[i].so_rcvbuf = *(int *)optval; if (sockets[i].so_rcvbuf > SO_MAX_BUF) sockets[i].so_rcvbuf = SO_MAX_BUF; (void) tcp_opt_set(sockets[i].pcb, level, option, optval, optlen); } else { errno = EINVAL; } break; case SO_SNDBUF: if (optlen == sizeof (int)) { sockets[i].so_sndbuf = *(int *)optval; if (sockets[i].so_sndbuf > SO_MAX_BUF) sockets[i].so_sndbuf = SO_MAX_BUF; (void) tcp_opt_set(sockets[i].pcb, level, option, optval, optlen); } else { errno = EINVAL; } break; case SO_LINGER: if (optlen == sizeof (struct linger)) { /* struct copy */ sockets[i].so_linger = *(struct linger *)optval; (void) tcp_opt_set(sockets[i].pcb, level, option, optval, optlen); } else { errno = EINVAL; } break; default: errno = ENOPROTOOPT; break; } break; } /* case SOL_SOCKET */ case IPPROTO_TCP: case IPPROTO_IP: { switch (option) { default: errno = ENOPROTOOPT; break; } break; } /* case IPPROTO_IP or IPPROTO_TCP */ default: errno = ENOPROTOOPT; break; } /* switch (level) */ if (errno != 0) return (-1); else return (0); } /* * Shut down part of a full-duplex connection. * * Only supported for TCP sockets */ int shutdown(int s, int how) { int sock_id; int i; errno = 0; if ((sock_id = so_check_fd(s, &errno)) == -1) return (-1); /* shutdown only supported for TCP sockets */ if (sockets[sock_id].type != INETBOOT_STREAM) { errno = EOPNOTSUPP; return (-1); } if (!(sockets[sock_id].so_state & SS_ISCONNECTED)) { errno = ENOTCONN; return (-1); } switch (how) { case 0: sockets[sock_id].so_state |= SS_CANTRCVMORE; break; case 1: sockets[sock_id].so_state |= SS_CANTSENDMORE; break; case 2: sockets[sock_id].so_state |= (SS_CANTRCVMORE | SS_CANTSENDMORE); break; default: errno = EINVAL; return (-1); } switch (sockets[sock_id].so_state & (SS_CANTRCVMORE | SS_CANTSENDMORE)) { case (SS_CANTRCVMORE | SS_CANTSENDMORE): /* Call lower level protocol close routine. */ for (i = TRANSPORT_LVL; i >= MEDIA_LVL; i--) { if (sockets[sock_id].close[i] != NULL) { (void) sockets[sock_id].close[i](sock_id); } } nuke_grams(&sockets[sock_id].inq); break; case SS_CANTRCVMORE: nuke_grams(&sockets[sock_id].inq); break; case SS_CANTSENDMORE: /* Call lower level protocol close routine. */ if (tcp_shutdown(sock_id) < 0) return (-1); break; default: errno = EINVAL; return (-1); } return (0); } /* * "close" a socket. */ int socket_close(int s) { int sock_id, i; errno = 0; if ((sock_id = so_check_fd(s, &errno)) == -1) return (-1); /* Call lower level protocol close routine. */ for (i = TRANSPORT_LVL; i >= MEDIA_LVL; i--) { if (sockets[sock_id].close[i] != NULL) { /* * Note that the close() routine of other * layers can return an error. But right * now, the only mechanism to report that * back is for the close() routine to set * the errno and socket_close() will return * an error. But the close operation will * not be stopped. */ (void) sockets[sock_id].close[i](sock_id); } } /* * Clear the input queue. This has to be done * after the lower level protocol close routines have been * called as they may want to do something about the queue. */ nuke_grams(&sockets[sock_id].inq); bzero((caddr_t)&sockets[sock_id], sizeof (struct inetboot_socket)); sockets[sock_id].type = INETBOOT_UNUSED; return (0); } /* * Read up to `nbyte' of data from socket `s' into `buf'; if non-zero, * then give up after `read_timeout' seconds. Returns the number of * bytes read, or -1 on failure. */ int socket_read(int s, void *buf, size_t nbyte, int read_timeout) { ssize_t n; uint_t start, diff; /* * keep calling non-blocking recvfrom until something received * or an error occurs */ start = prom_gettime(); for (;;) { n = recvfrom(s, buf, nbyte, MSG_DONTWAIT, NULL, NULL); if (n == -1 && errno == EWOULDBLOCK) { diff = (uint_t)((prom_gettime() - start) + 500) / 1000; if (read_timeout != 0 && diff > read_timeout) { errno = EINTR; return (-1); } } else { return (n); } } } /* * Write up to `nbyte' bytes of data from `buf' to the address pointed to * `addr' using socket `s'. Returns the number of bytes writte on success, * or -1 on failure. */ int socket_write(int s, const void *buf, size_t nbyte, struct sockaddr_in *addr) { return (sendto(s, buf, nbyte, 0, (struct sockaddr *)addr, sizeof (*addr))); } static int bind_check(int sock_id, const struct sockaddr *addr) { int k; struct sockaddr_in *in_addr = (struct sockaddr_in *)addr; /* Do not check for duplicate bind() if SO_REUSEADDR option is set. */ if (! (sockets[sock_id].so_opt & SO_REUSEADDR)) { for (k = 0; k < MAXSOCKET; k++) { if (sockets[k].type != INETBOOT_UNUSED && sockets[k].proto == sockets[sock_id].proto && sockets[k].bound) { if ((sockets[k].bind.sin_addr.s_addr == in_addr->sin_addr.s_addr) && (sockets[k].bind.sin_port == in_addr->sin_port)) { errno = EADDRINUSE; return (-1); } } } } return (0); } /* Assign a name to an unnamed socket. */ int bind(int s, const struct sockaddr *name, socklen_t namelen) { int i; errno = 0; if ((i = so_check_fd(s, &errno)) == -1) return (-1); if (name == NULL) { /* unbind */ if (sockets[i].bound) { bzero((caddr_t)&sockets[i].bind, sizeof (struct sockaddr_in)); sockets[i].bound = B_FALSE; } return (0); } if (namelen != sizeof (struct sockaddr_in) || name == NULL) { errno = EINVAL; return (-1); } if (name->sa_family != AF_INET) { errno = EAFNOSUPPORT; return (-1); } if (sockets[i].bound) { if (bcmp((caddr_t)&sockets[i].bind, (caddr_t)name, namelen) == 0) { /* attempt to bind to same address ok... */ return (0); } errno = EINVAL; /* already bound */ return (-1); } if (errno != 0) { return (-1); } /* Check for duplicate bind(). */ if (bind_check(i, name) < 0) return (-1); bcopy((caddr_t)name, (caddr_t)&sockets[i].bind, namelen); if (sockets[i].type == INETBOOT_STREAM) { if (tcp_bind(i) < 0) { return (-1); } } sockets[i].bound = B_TRUE; return (0); } static int quickbind(int sock_id) { int i; struct sockaddr_in addr; /* * XXX This needs more work. Right now, if ipv4_setipaddr() * have not been called, this will be wrong. But we need * something better. Need to be revisited. */ ipv4_getipaddr(&addr.sin_addr); addr.sin_family = AF_INET; for (i = SMALLEST_ANON_PORT; i <= LARGEST_ANON_PORT; i++) { addr.sin_port = htons(i); if (bind_check(sock_id, (struct sockaddr *)&addr) == 0) break; } /* Need to clear errno as it is probably set by bind_check(). */ errno = 0; if (i <= LARGEST_ANON_PORT) { bcopy((caddr_t)&addr, (caddr_t)&sockets[sock_id].bind, sizeof (struct sockaddr_in)); sockets[sock_id].bound = B_TRUE; #ifdef DEBUG printf("quick bind done addr %s port %d\n", inet_ntoa(sockets[sock_id].bind.sin_addr), ntohs(sockets[sock_id].bind.sin_port)); #endif return (0); } else { return (-1); } } int listen(int fd, int backlog) { int sock_id; errno = 0; if ((sock_id = so_check_fd(fd, &errno)) == -1) return (-1); if (sockets[sock_id].type != INETBOOT_STREAM) { errno = EOPNOTSUPP; return (-1); } if (sockets[sock_id].so_error != 0) { errno = sockets[sock_id].so_error; return (-1); } return (tcp_listen(sock_id, backlog)); } int accept(int fd, struct sockaddr *addr, socklen_t *addr_len) { int sock_id; int new_sd; errno = 0; if ((sock_id = so_check_fd(fd, &errno)) == -1) return (-1); if (sockets[sock_id].type != INETBOOT_STREAM) { errno = EOPNOTSUPP; return (-1); } if (sockets[sock_id].so_error != 0) { errno = sockets[sock_id].so_error; return (-1); } if ((new_sd = tcp_accept(sock_id, addr, addr_len)) == -1) return (-1); sock_id = so_check_fd(new_sd, &errno); sockets[sock_id].so_state |= SS_ISCONNECTED; return (new_sd); } int connect(int fd, const struct sockaddr *addr, socklen_t addr_len) { int sock_id; int so_type; errno = 0; if ((sock_id = so_check_fd(fd, &errno)) == -1) return (-1); so_type = sockets[sock_id].type; if (addr == NULL || addr_len == 0) { errno = EINVAL; return (-1); } /* Don't allow connect for raw socket. */ if (so_type == INETBOOT_RAW) { errno = EPROTONOSUPPORT; return (-1); } if (sockets[sock_id].so_state & SS_ISCONNECTED) { errno = EINVAL; return (-1); } if (sockets[sock_id].so_error != 0) { errno = sockets[sock_id].so_error; return (-1); } /* If the socket is not bound, we need to do a quick bind. */ if (!sockets[sock_id].bound) { /* For TCP socket, just call tcp_bind(). */ if (so_type == INETBOOT_STREAM) { if (tcp_bind(sock_id) < 0) return (-1); } else { if (quickbind(sock_id) < 0) { errno = EADDRNOTAVAIL; return (-1); } } } /* Should do some sanity check for addr .... */ bcopy((caddr_t)addr, &sockets[sock_id].remote, sizeof (struct sockaddr_in)); if (sockets[sock_id].type == INETBOOT_STREAM) { /* Call TCP connect routine. */ if (tcp_connect(sock_id) == 0) sockets[sock_id].so_state |= SS_ISCONNECTED; else { if (sockets[sock_id].so_error != 0) errno = sockets[sock_id].so_error; return (-1); } } else { sockets[sock_id].so_state |= SS_ISCONNECTED; } return (0); } /* Just a wrapper around recvfrom(). */ ssize_t recv(int s, void *buf, size_t len, int flags) { return (recvfrom(s, buf, len, flags, NULL, NULL)); } /* * Receive messages from a connectionless socket. Legal flags are 0 and * MSG_DONTWAIT. MSG_WAITALL is not currently supported. * * Returns length of message for success, -1 if error occurred. */ ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen) { int sock_id, i; ssize_t datalen, bytes = 0; struct inetgram *icp; enum SockType so_type; char *tmp_buf; mblk_t *mp; errno = 0; if ((sock_id = so_check_fd(s, &errno)) == -1) { errno = EINVAL; return (-1); } if (sockets[sock_id].type == INETBOOT_STREAM && !(sockets[sock_id].so_state & SS_ISCONNECTED)) { errno = ENOTCONN; return (-1); } if (buf == NULL || len == 0) { errno = EINVAL; return (-1); } /* Yup - MSG_WAITALL not implemented */ if ((flags & ~MSG_DONTWAIT) != 0) { errno = EINVAL; return (-1); } retry: if (sockets[sock_id].inq == NULL) { /* Go out and check the wire */ for (i = MEDIA_LVL; i < APP_LVL; i++) { if (sockets[sock_id].input[i] != NULL) { if (sockets[sock_id].input[i](sock_id) < 0) { if (sockets[sock_id].so_error != 0) { errno = sockets[sock_id].so_error; } return (-1); } } } } so_type = sockets[sock_id].type; /* Remove unknown inetgrams from the head of inq. Can this happen? */ while ((icp = sockets[sock_id].inq) != NULL) { if ((so_type == INETBOOT_DGRAM || so_type == INETBOOT_STREAM) && icp->igm_level != APP_LVL) { #ifdef DEBUG printf("recvfrom: unexpected level %d frame found\n", icp->igm_level); #endif /* DEBUG */ del_gram(&sockets[sock_id].inq, icp, B_TRUE); continue; } else { break; } } if (icp == NULL) { /* * Checking for error should be done everytime a lower layer * input routing is called. For example, if TCP gets a RST, * this should be reported asap. */ if (sockets[sock_id].so_state & SS_CANTRCVMORE) { if (sockets[sock_id].so_error != 0) { errno = sockets[sock_id].so_error; return (-1); } else { return (0); } } if ((flags & MSG_DONTWAIT) == 0) goto retry; /* wait forever */ /* no data */ errno = EWOULDBLOCK; return (-1); } if (from != NULL && fromlen != NULL) { switch (so_type) { case INETBOOT_STREAM: /* Need to copy from the socket's remote address. */ bcopy(&(sockets[sock_id].remote), from, MIN(*fromlen, sizeof (struct sockaddr_in))); break; case INETBOOT_RAW: case INETBOOT_DGRAM: default: if (*fromlen > sizeof (icp->igm_saddr)) *fromlen = sizeof (icp->igm_saddr); bcopy((caddr_t)&(icp->igm_saddr), (caddr_t)from, MIN(*fromlen, sizeof (struct sockaddr_in))); break; } } mp = icp->igm_mp; switch (so_type) { case INETBOOT_STREAM: /* * If the message has igm_id == TCP_CALLB_MAGIC_ID, we need * to drain the data held by tcp and try again. */ if (icp->igm_id == TCP_CALLB_MAGIC_ID) { del_gram(&sockets[sock_id].inq, icp, B_TRUE); tcp_rcv_drain_sock(sock_id); goto retry; } /* TCP should put only user data in the inetgram. */ tmp_buf = (char *)buf; while (len > 0 && icp != NULL) { datalen = mp->b_wptr - mp->b_rptr; if (len < datalen) { bcopy(mp->b_rptr, tmp_buf, len); bytes += len; mp->b_rptr += len; break; } else { bcopy(mp->b_rptr, tmp_buf, datalen); len -= datalen; bytes += datalen; tmp_buf += datalen; del_gram(&sockets[sock_id].inq, icp, B_TRUE); /* * If we have any embedded magic messages just * drop them. */ while ((icp = sockets[sock_id].inq) != NULL) { if (icp->igm_id != TCP_CALLB_MAGIC_ID) break; del_gram(&sockets[sock_id].inq, icp, B_TRUE); } if (icp == NULL) break; mp = icp->igm_mp; } } sockets[sock_id].so_rcvbuf += (int32_t)bytes; break; case INETBOOT_DGRAM: datalen = mp->b_wptr - mp->b_rptr; if (len < datalen) bytes = len; else bytes = datalen; bcopy(mp->b_rptr, buf, bytes); del_gram(&sockets[sock_id].inq, icp, B_TRUE); break; case INETBOOT_RAW: default: datalen = mp->b_wptr - mp->b_rptr; if (len < datalen) bytes = len; else bytes = datalen; bcopy(mp->b_rptr, buf, bytes); del_gram(&sockets[sock_id].inq, icp, B_TRUE); break; } #ifdef DEBUG printf("recvfrom(%d): data: (0x%x,%d)\n", sock_id, (icp != NULL) ? icp->igm_mp : 0, bytes); #endif /* DEBUG */ return (bytes); } /* Just a wrapper around sendto(). */ ssize_t send(int s, const void *msg, size_t len, int flags) { return (sendto(s, msg, len, flags, NULL, 0)); } /* * Transmit a message through a socket. * * Supported flags: MSG_DONTROUTE or 0. */ ssize_t sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) { enum SockType so_type; int sock_id; ssize_t bytes; errno = 0; if ((sock_id = so_check_fd(s, &errno)) == -1) { return (-1); } if (msg == NULL) { errno = EINVAL; return (-1); } so_type = sockets[sock_id].type; if ((flags & ~MSG_DONTROUTE) != 0) { errno = EINVAL; return (-1); } if (sockets[sock_id].so_error != 0) { errno = sockets[sock_id].so_error; return (-1); } if (to != NULL && to->sa_family != AF_INET) { errno = EAFNOSUPPORT; return (-1); } switch (so_type) { case INETBOOT_RAW: case INETBOOT_DGRAM: if (!(sockets[sock_id].so_state & SS_ISCONNECTED) && (to == NULL || tolen != sizeof (struct sockaddr_in))) { errno = EINVAL; return (-1); } bytes = dgram_sendto(sock_id, msg, len, flags, to, tolen); break; case INETBOOT_STREAM: if (!((sockets[sock_id].so_state & SS_ISCONNECTED) || (sockets[sock_id].so_state & SS_ISCONNECTING))) { errno = EINVAL; return (-1); } if (sockets[sock_id].so_state & SS_CANTSENDMORE) { errno = EPIPE; return (-1); } bytes = stream_sendto(sock_id, msg, len, flags); break; default: /* Should not happen... */ errno = EPROTOTYPE; return (-1); } return (bytes); } static ssize_t dgram_sendto(int i, const void *msg, size_t len, int flags, const struct sockaddr *to, int tolen) { struct inetgram oc; int l, offset; size_t tlen; mblk_t *mp; #ifdef DEBUG { struct sockaddr_in *sin = (struct sockaddr_in *)to; printf("sendto(%d): msg of length: %d sent to port %d and host: %s\n", i, len, ntohs(sin->sin_port), inet_ntoa(sin->sin_addr)); } #endif /* DEBUG */ nuke_grams(&sockets[i].inq); /* flush the input queue */ /* calculate offset for data */ offset = sockets[i].headerlen[MEDIA_LVL](NULL) + (sockets[i].headerlen[NETWORK_LVL])(NULL); bzero((caddr_t)&oc, sizeof (oc)); if (sockets[i].type != INETBOOT_RAW) { offset += (sockets[i].headerlen[TRANSPORT_LVL])(NULL); oc.igm_level = TRANSPORT_LVL; } else oc.igm_level = NETWORK_LVL; oc.igm_oflags = flags; if (to != NULL) { bcopy((caddr_t)to, (caddr_t)&oc.igm_saddr, tolen); } else { bcopy((caddr_t)&sockets[i].remote, (caddr_t)&oc.igm_saddr, sizeof (struct sockaddr_in)); } /* Get a legal source port if the socket isn't bound. */ if (sockets[i].bound == B_FALSE && ntohs(oc.igm_saddr.sin_port == 0)) { ((struct sockaddr_in *)&oc.igm_saddr)->sin_port = get_source_port(B_FALSE); } /* Round up to 16bit value for checksum purposes */ if (sockets[i].type == INETBOOT_DGRAM) { tlen = ((len + sizeof (uint16_t) - 1) & ~(sizeof (uint16_t) - 1)); } else tlen = len; if ((oc.igm_mp = allocb(tlen + offset, 0)) == NULL) { errno = ENOMEM; return (-1); } mp = oc.igm_mp; mp->b_rptr = mp->b_wptr += offset; bcopy((caddr_t)msg, mp->b_wptr, len); mp->b_wptr += len; for (l = TRANSPORT_LVL; l >= MEDIA_LVL; l--) { if (sockets[i].output[l] != NULL) { if (sockets[i].output[l](i, &oc) < 0) { freeb(mp); if (errno == 0) errno = EIO; return (-1); } } } freeb(mp); return (len); } /* ARGSUSED */ static ssize_t stream_sendto(int i, const void *msg, size_t len, int flags) { int cnt; assert(sockets[i].pcb != NULL); /* * Call directly TCP's send routine. We do this because TCP * needs to decide whether to send out the data. * * Note also that currently, TCP ignores all flags passed in for * TCP socket. */ if ((cnt = tcp_send(i, sockets[i].pcb, msg, len)) < 0) { if (sockets[i].so_error != 0) errno = sockets[i].so_error; return (-1); } else { return (cnt); } } /* * Returns ptr to the last inetgram in the list, or null if list is null */ struct inetgram * last_gram(struct inetgram *igp) { struct inetgram *wp; for (wp = igp; wp != NULL; wp = wp->igm_next) { if (wp->igm_next == NULL) return (wp); } return (NULL); } /* * Adds an inetgram or list of inetgrams to the end of the list. */ void add_grams(struct inetgram **igpp, struct inetgram *newgp) { struct inetgram *wp; if (newgp == NULL) return; if (*igpp == NULL) *igpp = newgp; else { wp = last_gram(*igpp); wp->igm_next = newgp; } } /* * Nuke a whole list of grams. */ void nuke_grams(struct inetgram **lgpp) { while (*lgpp != NULL) del_gram(lgpp, *lgpp, B_TRUE); } /* * Remove the referenced inetgram. List is altered accordingly. Destroy the * referenced inetgram if freeit is B_TRUE. */ void del_gram(struct inetgram **lgpp, struct inetgram *igp, int freeit) { struct inetgram *wp, *pp = NULL; if (lgpp == NULL || igp == NULL) return; wp = *lgpp; while (wp != NULL) { if (wp == igp) { /* detach wp from the list */ if (*lgpp == wp) *lgpp = (*lgpp)->igm_next; else pp->igm_next = wp->igm_next; igp->igm_next = NULL; if (freeit) { if (igp->igm_mp != NULL) freeb(igp->igm_mp); bkmem_free((caddr_t)igp, sizeof (struct inetgram)); } break; } pp = wp; wp = wp->igm_next; } } struct nct_t nct[] = { "bootp", NCT_BOOTP_DHCP, "dhcp", NCT_BOOTP_DHCP, "rarp", NCT_RARP_BOOTPARAMS, "manual", NCT_MANUAL }; int nct_entries = sizeof (nct) / sizeof (nct[0]); /* * Figure out from the bootpath what kind of network configuration strategy * we should use. Returns the network config strategy. */ int get_netconfig_strategy(void) { int i; #define ISSPACE(c) (c == ' ' || c == '\t' || c == '\n' || c == '\0') char lbootpath[OBP_MAXPATHLEN]; char net_options[NCT_BUFSIZE]; char *op, *nop, *sp; pnode_t cn; int proplen; /* If the PROM DHCP cache exists, we're done */ if (prom_cached_reply(B_TRUE)) return (NCT_BOOTP_DHCP); /* * Newer (version 4) PROMs will put the name in the * "net-config-strategy" property. */ cn = prom_finddevice("/chosen"); if ((proplen = prom_getproplen(cn, "net-config-strategy")) < sizeof (net_options)) { (void) prom_getprop(cn, "net-config-strategy", net_options); net_options[proplen] = '\0'; } else { /* * We're reduced to sacanning bootpath for the prototol to use. * Since there was no "net-config-strategy" property, this is * an old PROM, so we need to excise any extraneous key/value * initializations from bootpath[]. */ for (op = prom_bootpath(), sp = lbootpath; op != NULL && !ISSPACE(*op); sp++, op++) *sp = *op; *sp = '\0'; /* find the last '/' (in the device path) */ if ((op = strrchr(lbootpath, '/')) == NULL) /* last '/' */ op = lbootpath; else op++; /* then look for the ':' separating it from the protocol */ while (*op != ':' && *op != '\0') op++; if (*op == ':') { for (nop = net_options, op++; *op != '\0' && *op != '/' && !ISSPACE(*op) && nop < &net_options[NCT_BUFSIZE]; nop++, op++) *nop = *op; *nop = '\0'; } else net_options[0] = '\0'; } #undef ISSPACE for (i = 0; i < nct_entries; i++) if (strcmp(net_options, nct[i].p_name) == 0) return (nct[i].p_id); return (NCT_DEFAULT); } /* Modified STREAM routines for ease of porting core TCP code. */ /*ARGSUSED*/ mblk_t * allocb(size_t size, uint_t pri) { unsigned char *base; mblk_t *mp; if ((mp = (mblk_t *)bkmem_zalloc(sizeof (mblk_t))) == NULL) return (NULL); if ((base = (unsigned char *)bkmem_zalloc(size)) == NULL) return (NULL); mp->b_next = mp->b_prev = mp->b_cont = NULL; mp->b_rptr = mp->b_wptr = mp->b_datap = (unsigned char *)base; mp->b_size = size; return (mp); } void freeb(mblk_t *mp) { #ifdef DEBUG printf("freeb datap %x\n", mp->b_datap); #endif bkmem_free((caddr_t)(mp->b_datap), mp->b_size); #ifdef DEBUG printf("freeb mp %x\n", mp); #endif bkmem_free((caddr_t)mp, sizeof (mblk_t)); } void freemsg(mblk_t *mp) { while (mp) { mblk_t *mp_cont = mp->b_cont; freeb(mp); mp = mp_cont; } } mblk_t * copyb(mblk_t *bp) { mblk_t *nbp; unsigned char *ndp; assert((uintptr_t)(bp->b_wptr - bp->b_rptr) >= 0); if (!(nbp = allocb(bp->b_size, 0))) return (NULL); nbp->b_cont = NULL; ndp = nbp->b_datap; nbp->b_rptr = ndp + (bp->b_rptr - bp->b_datap); nbp->b_wptr = nbp->b_rptr + (bp->b_wptr - bp->b_rptr); bcopy(bp->b_datap, nbp->b_datap, bp->b_size); return (nbp); } /* To simplify things, dupb() is implemented as copyb(). */ mblk_t * dupb(mblk_t *mp) { return (copyb(mp)); } /* * get number of data bytes in message */ size_t msgdsize(mblk_t *bp) { size_t count = 0; for (; bp != NULL; bp = bp->b_cont) { assert(bp->b_wptr >= bp->b_rptr); count += bp->b_wptr - bp->b_rptr; } return (count); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2002-2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Internal socket-specific definitions */ #ifndef _SOCKET_IMPL_H #define _SOCKET_IMPL_H #pragma ident "%Z%%M% %I% %E% SMI" #ifdef __cplusplus extern "C" { #endif #include #include /* * Socket support definitions */ #define MAXSOCKET (10) #define SOCKETTYPE (65536) #define MEDIA_LVL 0 #define NETWORK_LVL 1 #define TRANSPORT_LVL 2 #define APP_LVL 3 /* Anonymous ports assigned by socket. */ #define SMALLEST_ANON_PORT 32768 #define LARGEST_ANON_PORT ((64 * 1024) - 1) /* Socket state bits. */ #define SS_ISCONNECTED 0x000001 /* socket connected to a peer */ #define SS_ISCONNECTING 0x000002 /* in process of connecting to peer */ #define SS_CANTRCVMORE 0x000010 /* can't receive more data from peer */ #define SS_CANTSENDMORE 0x000008 /* can't send more data to peer */ enum { FALSE, TRUE }; enum SockType { INETBOOT_UNUSED, INETBOOT_DGRAM, INETBOOT_RAW, INETBOOT_STREAM }; enum Ports { SOURCE, DESTINATION }; #define FD_TO_SOCKET(v) ((v) - SOCKETTYPE) /* * Message block descriptor copied from usr/src/uts/common/sys/stream.h. * We need to do that to simplify the porting of TCP code from core * kernel to inetboot. Note that fields which are not used by TCP * code are removed. */ typedef struct msgb { struct msgb *b_next; struct msgb *b_prev; struct msgb *b_cont; unsigned char *b_rptr; unsigned char *b_wptr; unsigned char *b_datap; size_t b_size; } mblk_t; /* Modified stream routines to ease TCP porting. */ extern mblk_t *allocb(size_t, uint_t); extern mblk_t *dupb(mblk_t *); extern void freeb(mblk_t *); extern void freemsg(mblk_t *); extern size_t msgdsize(mblk_t *); /* * "target" is needed for input prior to IP address assignment. It may * seem redundant given the binding information contained in the socket, * but that's only true if we have an IP address. If we don't, and we * try DHCP, we'll try to udp checksum using INADDR_ANY as the destination * IP address, when in fact the destination IP address was the IP address * we were OFFERED/Assigned. */ struct inetgram { /* Common */ struct sockaddr_in igm_saddr; /* source address info */ int igm_level; /* Stack level (LVL) of data */ mblk_t *igm_mp; struct inetgram *igm_next; /* next inetgram in list */ union { struct { /* Input specific */ struct in_addr in_t; uint16_t in_i; } _IN_un; struct { /* Output specific */ struct in_addr out_r; int out_f; } _OUT_un; } _i_o_inet; #define igm_target _i_o_inet._IN_un.in_t /* See above comment block */ #define igm_id _i_o_inet._IN_un.in_i /* IP id */ #define igm_router _i_o_inet._OUT_un.out_r /* first router IP ... */ #define igm_oflags _i_o_inet._OUT_un.out_f /* flag: 0 or MSG_DONTROUTE */ }; struct inetboot_socket { enum SockType type; /* socket type */ uint8_t proto; /* ip protocol */ int out_flags; /* 0 or MSG_DONTROUTE */ boolean_t bound; /* boolean */ uint32_t so_state; /* Socket state */ int so_error; /* Socket error */ struct sockaddr_in bind; /* Binding info */ struct sockaddr_in remote; /* Remote address */ struct inetgram *inq; /* input queue */ int so_sndbuf; /* max send buf size */ int so_rcvbuf; /* max receive buf size */ struct linger so_linger; /* close linger time */ uint32_t in_timeout; /* Input timeout (msec) */ uint32_t so_opt; /* socket level option */ int (*headerlen[APP_LVL])(struct inetgram *); int (*input[APP_LVL])(int); int (*output[APP_LVL])(int, struct inetgram *); int (*close[APP_LVL])(int); in_port_t (*ports)(uint16_t *, enum Ports); void *pcb; /* Protocol control block */ }; extern struct inetboot_socket sockets[MAXSOCKET]; extern void add_grams(struct inetgram **, struct inetgram *); extern void del_gram(struct inetgram **, struct inetgram *, int); extern void nuke_grams(struct inetgram **); extern struct inetgram *last_gram(struct inetgram *); extern int so_check_fd(int, int *); #ifdef __cplusplus } #endif #endif /* _SOCKET_IMPL_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Socket-specific definitions */ #ifndef _SOCKET_INET_H #define _SOCKET_INET_H #pragma ident "%Z%%M% %I% %E% SMI" #include #include #ifdef __cplusplus extern "C" { #endif /* Network configuration protocol definitions */ enum nc_type { NCT_BOOTP_DHCP, NCT_RARP_BOOTPARAMS, NCT_MANUAL }; struct nct_t { char *p_name; enum nc_type p_id; }; #define NCT_DEFAULT NCT_RARP_BOOTPARAMS #define NCT_BUFSIZE (64) extern struct nct_t nct[]; extern int nct_entries; /* * Dynamic/private ports can be allocated in the range of 49152-65535. * Source: IANA (www.iana.org) port numbers. */ #define IPPORT_DYNAMIC_START 49152 extern int dontroute; extern int socket_read(int, void *, size_t, int); extern int socket_write(int, const void *, size_t, struct sockaddr_in *); extern int socket_close(int); extern int get_netconfig_strategy(void); extern in_port_t get_source_port(boolean_t); #ifdef __cplusplus } #endif #endif /* _SOCKET_INET_H */