ext/sctp/socket.c in sctp-socket-0.0.5 vs ext/sctp/socket.c in sctp-socket-0.1.0

- old
+ new

@@ -19,36 +19,23 @@ VALUE v_sockaddr_in_struct; VALUE v_sctp_status_struct; VALUE v_sctp_rtoinfo_struct; VALUE v_sctp_associnfo_struct; VALUE v_sctp_default_send_params_struct; +VALUE v_sctp_event_subscribe_struct; +VALUE v_sctp_receive_info_struct; +VALUE v_sctp_peer_addr_params_struct; +VALUE v_sender_dry_event_struct; #if !defined(IOV_MAX) #if defined(_SC_IOV_MAX) #define IOV_MAX (sysconf(_SC_IOV_MAX)) #else -// Assume infinity, or let the syscall return with error #define IOV_MAX INT_MAX #endif #endif -#define ARY2IOVEC(iov,ary) \ - do { \ - VALUE *cur; \ - struct iovec *tmp; \ - long n; \ - cur = RARRAY_PTR(ary); \ - n = RARRAY_LEN(ary); \ - iov = tmp = alloca(sizeof(struct iovec) * n); \ - for (; --n >= 0; tmp++, cur++) { \ - if (TYPE(*cur) != T_STRING) \ - rb_raise(rb_eArgError, "must be an array of strings"); \ - tmp->iov_base = RSTRING_PTR(*cur); \ - tmp->iov_len = RSTRING_LEN(*cur); \ - } \ - } while (0) - // TODO: Yes, I know I need to update the signature. VALUE convert_sockaddr_in_to_struct(struct sockaddr_in* addr){ char ipbuf[INET6_ADDRSTRLEN]; if(addr->sin_family == AF_INET6) @@ -92,29 +79,29 @@ * * socket1 = SCTP::Socket.new * socket2 = SCTP::Socket.new(Socket::AF_INET, Socket::SOCK_STREAM) */ static VALUE rsctp_init(int argc, VALUE* argv, VALUE self){ - int sock_fd; + int fileno; VALUE v_domain, v_type; rb_scan_args(argc, argv, "02", &v_domain, &v_type); if(NIL_P(v_domain)) v_domain = INT2NUM(AF_INET); if(NIL_P(v_type)) v_type = INT2NUM(SOCK_SEQPACKET); - sock_fd = socket(NUM2INT(v_domain), NUM2INT(v_type), IPPROTO_SCTP); + fileno = socket(NUM2INT(v_domain), NUM2INT(v_type), IPPROTO_SCTP); - if(sock_fd < 0) + if(fileno < 0) rb_raise(rb_eSystemCallError, "socket: %s", strerror(errno)); rb_iv_set(self, "@domain", v_domain); rb_iv_set(self, "@type", v_type); - rb_iv_set(self, "@sock_fd", INT2NUM(sock_fd)); + rb_iv_set(self, "@fileno", INT2NUM(fileno)); rb_iv_set(self, "@association_id", INT2NUM(0)); return self; } @@ -129,23 +116,23 @@ * Example: * * socket = SCTP::Socket.new * * # Bind 2 addresses - * socket.bind(:port => 64325, :addresses => ['10.0.4.5', '10.0.5.5']) + * socket.bindx(:port => 64325, :addresses => ['10.0.4.5', '10.0.5.5']) * * # Remove 1 later - * socket.bind(:addresses => ['10.0.4.5'], :flags => SCTP::Socket::BINDX_REM_ADDR) + * socket.bindx(:addresses => ['10.0.4.5'], :flags => SCTP::Socket::BINDX_REM_ADDR) * * If no addresses are specified, then it will bind to all available interfaces. If * no port is specified, then one will be assigned by the host. * * Returns the port that it was bound to. */ -static VALUE rsctp_bind(int argc, VALUE* argv, VALUE self){ +static VALUE rsctp_bindx(int argc, VALUE* argv, VALUE self){ struct sockaddr_in addrs[8]; - int i, sock_fd, num_ip, flags, domain, port; + int i, fileno, num_ip, flags, domain, port; VALUE v_addresses, v_port, v_flags, v_address, v_options; rb_scan_args(argc, argv, "01", &v_options); bzero(&addrs, sizeof(addrs)); @@ -171,11 +158,11 @@ num_ip = 1; else num_ip = RARRAY_LEN(v_addresses); domain = NUM2INT(rb_iv_get(self, "@domain")); - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); + fileno = NUM2INT(rb_iv_get(self, "@fileno")); if(num_ip > 1){ for(i = 0; i < num_ip; i++){ v_address = RARRAY_PTR(v_addresses)[i]; addrs[i].sin_family = domain; @@ -187,40 +174,43 @@ addrs[0].sin_family = domain; addrs[0].sin_port = htons(port); addrs[0].sin_addr.s_addr = htonl(INADDR_ANY); } - if(sctp_bindx(sock_fd, (struct sockaddr *) addrs, num_ip, flags) != 0) + if(sctp_bindx(fileno, (struct sockaddr *) addrs, num_ip, flags) != 0) rb_raise(rb_eSystemCallError, "sctp_bindx: %s", strerror(errno)); if(port == 0){ struct sockaddr_in sin; socklen_t len = sizeof(sin); + bzero(&sin, len); - if(getsockname(sock_fd, (struct sockaddr *)&sin, &len) == -1) + if(getsockname(fileno, (struct sockaddr *)&sin, &len) == -1) rb_raise(rb_eSystemCallError, "getsockname: %s", strerror(errno)); port = sin.sin_port; } + rb_iv_set(self, "@port", INT2NUM(port)); + return INT2NUM(port); } /* * Connect the socket to a multihomed peer via the provided array of addresses * using the domain specified in the constructor. You must also specify the port. * * Example: * * socket = SCTP::Socket.new - * socket.connect(:port => 62354, :addresses => ['10.0.4.5', '10.0.5.5']) + * socket.connectx(:port => 62354, :addresses => ['10.0.4.5', '10.0.5.5']) * * Note that this will also set/update the object's association_id. */ -static VALUE rsctp_connect(int argc, VALUE* argv, VALUE self){ +static VALUE rsctp_connectx(int argc, VALUE* argv, VALUE self){ struct sockaddr_in addrs[8]; - int i, num_ip, sock_fd; + int i, num_ip, fileno; sctp_assoc_t assoc; VALUE v_address, v_domain, v_options, v_addresses, v_port; rb_scan_args(argc, argv, "01", &v_options); @@ -248,13 +238,13 @@ addrs[i].sin_family = NUM2INT(v_domain); addrs[i].sin_port = htons(NUM2INT(v_port)); addrs[i].sin_addr.s_addr = inet_addr(StringValueCStr(v_address)); } - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); + fileno = NUM2INT(rb_iv_get(self, "@fileno")); - if(sctp_connectx(sock_fd, (struct sockaddr *) addrs, num_ip, &assoc) < 0) + if(sctp_connectx(fileno, (struct sockaddr *) addrs, num_ip, &assoc) < 0) rb_raise(rb_eSystemCallError, "sctp_connectx: %s", strerror(errno)); rb_iv_set(self, "@association_id", INT2NUM(assoc)); return self; @@ -267,35 +257,60 @@ * * socket = SCTP::Socket.new * socket.close */ static VALUE rsctp_close(VALUE self){ - VALUE v_sock_fd = rb_iv_get(self, "@sock_fd"); + VALUE v_fileno = rb_iv_get(self, "@fileno"); - if(close(NUM2INT(v_sock_fd))) + if(close(NUM2INT(v_fileno))) rb_raise(rb_eSystemCallError, "close: %s", strerror(errno)); return self; } /* - * Return an array of all addresses of a peer. + * Return an array of all addresses of a peer of the current socket + * and association number. + * + * You may optionally pass a assocation fileno and association ID. Typically + * this information would come from the peeloff method. + * + * Example: + * + * socket = SCTP::Socket.new + * # ... + * p socket.getpeernames + * + * info = socket.recvmsg + * association_fileno = socket.peeloff(info.association_id) + * + * p socket.getpeernames(association_fileno, info.association_id) */ -static VALUE rsctp_getpeernames(VALUE self){ +static VALUE rsctp_getpeernames(int argc, VALUE* argv, VALUE self){ sctp_assoc_t assoc_id; struct sockaddr* addrs; - int i, sock_fd, num_addrs; + int i, fileno, num_addrs; char str[16]; + VALUE v_fileno, v_association_id; VALUE v_array = rb_ary_new(); bzero(&addrs, sizeof(addrs)); - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); - assoc_id = NUM2INT(rb_iv_get(self, "@association_id")); + rb_scan_args(argc, argv, "02", &v_fileno, &v_association_id); - num_addrs = sctp_getpaddrs(sock_fd, assoc_id, &addrs); + if(NIL_P(v_fileno)) + fileno = NUM2INT(rb_iv_get(self, "@fileno")); + else + fileno = NUM2INT(v_fileno); + if(NIL_P(v_association_id)) + assoc_id = NUM2INT(rb_iv_get(self, "@association_id")); + else + assoc_id = NUM2INT(v_association_id); + + num_addrs = sctp_getpaddrs(fileno, assoc_id, &addrs); + if(num_addrs < 0){ sctp_freepaddrs(addrs); rb_raise(rb_eSystemCallError, "sctp_getpaddrs: %s", strerror(errno)); } @@ -316,25 +331,40 @@ * Example: * * socket = SCTP::Socket.new * socket.bind(:addresses => ['10.0.4.5', '10.0.5.5']) * socket.getlocalnames # => ['10.0.4.5', '10.0.5.5']) + * + * # or get info from a peeled off association... + * + * assoc_fileno = socket.peeloff(some_association_id) + * socket.getlocalnames(assoc_fileno, some_association_id) */ -static VALUE rsctp_getlocalnames(VALUE self){ +static VALUE rsctp_getlocalnames(int argc, VALUE* argv, VALUE self){ sctp_assoc_t assoc_id; struct sockaddr* addrs; - int i, sock_fd, num_addrs; + int i, fileno, num_addrs; char str[16]; + VALUE v_assoc_fileno, v_assoc_id; VALUE v_array = rb_ary_new(); bzero(&addrs, sizeof(addrs)); - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); - assoc_id = NUM2INT(rb_iv_get(self, "@association_id")); + rb_scan_args(argc, argv, "02", &v_assoc_fileno, &v_assoc_id); - num_addrs = sctp_getladdrs(sock_fd, assoc_id, &addrs); + if(NIL_P(v_assoc_fileno)) + fileno = NUM2INT(rb_iv_get(self, "@fileno")); + else + fileno = NUM2INT(v_assoc_fileno); + if(NIL_P(v_assoc_id)) + assoc_id = NUM2INT(rb_iv_get(self, "@association_id")); + else + assoc_id = NUM2INT(v_assoc_id); + + num_addrs = sctp_getladdrs(fileno, assoc_id, &addrs); + if(num_addrs < 0){ sctp_freeladdrs(addrs); rb_raise(rb_eSystemCallError, "sctp_getladdrs: %s", strerror(errno)); } @@ -348,41 +378,104 @@ return v_array; } #ifdef HAVE_SCTP_SENDV -static VALUE rsctp_sendv(VALUE self, VALUE v_messages){ - struct iovec* iov; - struct sockaddr* addrs[8]; +/* + * Transmit a message to an SCTP endpoint using a gather-write. The following + * hash of options is permitted: + * + * * message - An array of strings that will be joined into a single message. + * * addresses - An array of IP addresses to setup an association to send the message. + * * info_type - The type of information provided. The default is SCTP_SENDV_SNDINFO. + * + * Example: + * + * socket = SCTP::Socket.new + * + * socket.sendv + * :message => ['Hello ', 'World.'], + * :addresses => ['10.0.5.4', '10.0.6.4'], + * :info_type => SCTP::Socket:::SCTP_SENDV_SNDINFO + * ) + * + * CAVEAT: Currently addresses does not work, and info_type is not yet supported. + * + * Returns the number of bytes sent. + */ +static VALUE rsctp_sendv(VALUE self, VALUE v_options){ + VALUE v_msg, v_message, v_addresses; + struct iovec iov[IOV_MAX]; + struct sockaddr_in* addrs; struct sctp_sndinfo info; - int sock_fd, num_bytes, size; + int i, fileno, num_bytes, size, num_ip; - Check_Type(v_messages, T_ARRAY); - bzero(&addrs, sizeof(addrs)); + Check_Type(v_options, T_HASH); - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); + bzero(&iov, sizeof(iov)); + bzero(&info, sizeof(info)); - Check_Type(v_messages, T_ARRAY); - size = RARRAY_LEN(v_messages); + v_message = rb_hash_aref2(v_options, "message"); + v_addresses = rb_hash_aref2(v_options, "addresses"); + if(!NIL_P(v_message)) + Check_Type(v_message, T_ARRAY); + + if(!NIL_P(v_addresses)){ + Check_Type(v_addresses, T_ARRAY); + num_ip = RARRAY_LEN(v_addresses); + addrs = (struct sockaddr_in*)alloca(sizeof(struct sockaddr_in) * num_ip); + } + else{ + addrs = NULL; + num_ip = 0; + } + + fileno = NUM2INT(rb_iv_get(self, "@fileno")); + size = RARRAY_LEN(v_message); + if(!size) rb_raise(rb_eArgError, "Must contain at least one message"); if(size > IOV_MAX) rb_raise(rb_eArgError, "Array size is greater than IOV_MAX"); - ARY2IOVEC(iov, v_messages); - info.snd_flags = SCTP_UNORDERED; info.snd_assoc_id = NUM2INT(rb_iv_get(self, "@association_id")); + if(!NIL_P(v_addresses)){ + int i, port; + VALUE v_address, v_port; + + v_port = NUM2INT(rb_iv_get(self, "@port")); + + if(NIL_P(v_port)) + port = 0; + else + port = NUM2INT(v_port); + + for(i = 0; i < num_ip; i++){ + v_address = RARRAY_PTR(v_addresses)[i]; + addrs->sin_family = NUM2INT(rb_iv_get(self, "@domain")); + addrs->sin_port = htons(port); + addrs->sin_addr.s_addr = inet_addr(StringValueCStr(v_address)); + addrs += sizeof(struct sockaddr_in); + } + } + + for(i = 0; i < size; i++){ + v_msg = RARRAY_PTR(v_message)[i]; + iov[i].iov_base = StringValueCStr(v_msg); + iov[i].iov_len = RSTRING_LEN(v_msg); + } + num_bytes = sctp_sendv( - sock_fd, + fileno, iov, size, - NULL, - 0, + (struct sockaddr*)addrs, + num_ip, &info, sizeof(info), SCTP_SENDV_SNDINFO, 0 ); @@ -392,10 +485,78 @@ return INT2NUM(num_bytes); } #endif +#ifdef HAVE_SCTP_RECVV +static VALUE rsctp_recvv(int argc, VALUE* argv, VALUE self){ + VALUE v_flags; + int fileno, flags, bytes, on; + uint infotype; + socklen_t infolen; + struct iovec iov[1]; + struct sctp_rcvinfo info; + char buffer[1024]; + + bzero(&iov, sizeof(iov)); + bzero(&info, sizeof(info)); + bzero(&buffer, sizeof(buffer)); + + iov->iov_base = buffer; + iov->iov_len = sizeof(buffer); + + rb_scan_args(argc, argv, "01", &v_flags); + + fileno = NUM2INT(rb_iv_get(self, "@fileno")); + + if(NIL_P(v_flags)) + flags = 0; + else + flags = NUM2INT(v_flags); + + on = 1; + if(setsockopt(fileno, IPPROTO_SCTP, SCTP_RECVRCVINFO, &on, sizeof(on)) < 0) + rb_raise(rb_eSystemCallError, "setsockopt: %s", strerror(errno)); + + infolen = sizeof(struct sctp_rcvinfo); + infotype = 0; + + bytes = sctp_recvv( + fileno, + iov, + 1, + NULL, + NULL, + &info, + &infolen, + &infotype, + &flags + ); + + if(bytes < 0) + rb_raise(rb_eSystemCallError, "sctp_recvv: %s", strerror(errno)); + + if(infotype != SCTP_RECVV_RCVINFO){ + return Qnil; + } + else{ + return rb_struct_new( + v_sctp_receive_info_struct, + rb_str_new2(iov->iov_base), + UINT2NUM(info.rcv_sid), + UINT2NUM(info.rcv_ssn), + UINT2NUM(info.rcv_flags), + UINT2NUM(info.rcv_ppid), + UINT2NUM(info.rcv_tsn), + UINT2NUM(info.rcv_cumtsn), + UINT2NUM(info.rcv_context), + UINT2NUM(info.rcv_assoc_id) + ); + } +} +#endif + /* * Send a message on an already-connected socket to a specific association. * * Example: * @@ -408,11 +569,11 @@ */ static VALUE rsctp_send(VALUE self, VALUE v_options){ uint16_t stream; uint32_t ppid, send_flags, ctrl_flags, ttl, context; ssize_t num_bytes; - int sock_fd; + int fileno; sctp_assoc_t assoc_id; struct sctp_sndrcvinfo info; VALUE v_msg, v_stream, v_ppid, v_context, v_send_flags, v_ctrl_flags, v_ttl, v_assoc_id; Check_Type(v_options, T_HASH); @@ -469,14 +630,14 @@ info.sinfo_ppid = ppid; info.sinfo_context = context; info.sinfo_timetolive = ttl; info.sinfo_assoc_id = assoc_id; - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); + fileno = NUM2INT(rb_iv_get(self, "@fileno")); num_bytes = sctp_send( - sock_fd, + fileno, StringValueCStr(v_msg), RSTRING_LEN(v_msg), &info, ctrl_flags ); @@ -489,40 +650,42 @@ /* * Transmit a message to an SCTP endpoint. The following hash of options * is permitted: * - * :message -> The message to send to the endpoint. Mandatory. - * :stream -> The SCTP stream number you wish to send the message on. - * :to -> An array of addresses to send the message to. - * :context -> The default context used for the sendmsg call if the send fails. - * :ppid -> The payload protocol identifier that is passed to the peer endpoint. - * :flags -> A bitwise integer that contain one or more values that control behavior. + * :message -> The message to send to the endpoint. Mandatory. + * :stream -> The SCTP stream number you wish to send the message on. + * :addresses -> An array of addresses to send the message to. + * :context -> The default context used for the sendmsg call if the send fails. + * :ppid -> The payload protocol identifier that is passed to the peer endpoint. + * :flags -> A bitwise integer that contain one or more values that control behavior. * - * Note that the :to option is not mandatory in a one-to-one (SOCK_STREAM) + * Note that the :addresses option is not mandatory in a one-to-one (SOCK_STREAM) * socket connection. However, it must have been set previously via the * connect method. * * Example: * * socket = SCTP::Socket.new * * socket.sendmsg( - * :message => "Hello World!", - * :stream => 3, - * :flags => SCTP::Socket::SCTP_UNORDERED | SCTP::Socket::SCTP_SENDALL, - * :ttl => 100, - * :to => ['10.0.5.4', '10.0.6.4'] + * :message => "Hello World!", + * :stream => 3, + * :flags => SCTP::Socket::SCTP_UNORDERED | SCTP::Socket::SCTP_SENDALL, + * :ttl => 100, + * :addresses => ['10.0.5.4', '10.0.6.4'] * ) + * + * Returns the number of bytes sent. */ static VALUE rsctp_sendmsg(VALUE self, VALUE v_options){ VALUE v_msg, v_ppid, v_flags, v_stream, v_ttl, v_context, v_addresses; uint16_t stream; uint32_t ppid, flags, ttl, context; ssize_t num_bytes; struct sockaddr_in addrs[8]; - int sock_fd, size; + int fileno, size; Check_Type(v_options, T_HASH); bzero(&addrs, sizeof(addrs)); @@ -585,14 +748,14 @@ } else{ size = 0; } - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); + fileno = NUM2INT(rb_iv_get(self, "@fileno")); num_bytes = sctp_sendmsg( - sock_fd, + fileno, StringValueCStr(v_msg), RSTRING_LEN(v_msg), (struct sockaddr*)addrs, size, ppid, @@ -629,27 +792,30 @@ */ static VALUE rsctp_recvmsg(int argc, VALUE* argv, VALUE self){ VALUE v_flags, v_notification, v_message; struct sctp_sndrcvinfo sndrcvinfo; struct sockaddr_in clientaddr; - int flags, bytes, sock_fd; + int flags, bytes, fileno; char buffer[1024]; // TODO: Let this be configurable? socklen_t length; rb_scan_args(argc, argv, "01", &v_flags); if(NIL_P(v_flags)) flags = 0; else flags = NUM2INT(v_flags); - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); + fileno = NUM2INT(rb_iv_get(self, "@fileno")); length = sizeof(struct sockaddr_in); + bzero(buffer, sizeof(buffer)); + bzero(&clientaddr, sizeof(clientaddr)); + bzero(&sndrcvinfo, sizeof(sndrcvinfo)); bytes = sctp_recvmsg( - sock_fd, + fileno, buffer, sizeof(buffer), (struct sockaddr*)&clientaddr, &length, &sndrcvinfo, @@ -748,10 +914,11 @@ v_temp[i] = UINT2NUM(snp->sn_remote_error.sre_data[i]); } v_notification = rb_struct_new(v_remote_error_struct, UINT2NUM(snp->sn_remote_error.sre_type), + UINT2NUM(snp->sn_remote_error.sre_flags), UINT2NUM(snp->sn_remote_error.sre_length), UINT2NUM(snp->sn_remote_error.sre_error), UINT2NUM(snp->sn_remote_error.sre_assoc_id), rb_ary_new4(snp->sn_remote_error.sre_length, v_temp) ); @@ -829,10 +996,18 @@ UINT2NUM(snp->sn_authkey_event.auth_keynumber), UINT2NUM(snp->sn_authkey_event.auth_indication), UINT2NUM(snp->sn_authkey_event.auth_assoc_id) ); break; + case SCTP_SENDER_DRY_EVENT: + v_notification = rb_struct_new(v_sender_dry_event_struct, + UINT2NUM(snp->sn_sender_dry_event.sender_dry_type), + UINT2NUM(snp->sn_sender_dry_event.sender_dry_flags), + UINT2NUM(snp->sn_sender_dry_event.sender_dry_length), + UINT2NUM(snp->sn_sender_dry_event.sender_dry_assoc_id) + ); + break; } } if(NIL_P(v_notification)) v_message = rb_str_new(buffer, bytes); @@ -860,30 +1035,30 @@ * socket = SCTP::Socket.new * socket.set_initmsg(:output_streams => 5, :input_streams => 5, :max_attempts => 4, :timeout => 30) * * The following parameters can be configured: * - * :output_streams - The number of outbound SCTP streams an application would like to request. - * :input_streams - The maximum number of inbound streams an application is prepared to allow. - * :max_attempts - How many times the the SCTP stack should send the initial INIT message before it's considered unreachable. - * :timeout - The maximum RTO value for the INIT timer. + * :output_streams - The number of outbound SCTP streams an application would like to request. + * :input_streams - The maximum number of inbound streams an application is prepared to allow. + * :max_attempts - How many times the the SCTP stack should send the initial INIT message before it's considered unreachable. + * :timeout - The maximum RTO value for the INIT timer. * * By default these values are set to zero (i.e. ignored). */ static VALUE rsctp_set_initmsg(VALUE self, VALUE v_options){ - int sock_fd; + int fileno; struct sctp_initmsg initmsg; VALUE v_output, v_input, v_attempts, v_timeout; bzero(&initmsg, sizeof(initmsg)); v_output = rb_hash_aref2(v_options, "output_streams"); v_input = rb_hash_aref2(v_options, "input_streams"); v_attempts = rb_hash_aref2(v_options, "max_attempts"); v_timeout = rb_hash_aref2(v_options, "timeout"); - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); + fileno = NUM2INT(rb_iv_get(self, "@fileno")); if(!NIL_P(v_output)) initmsg.sinit_num_ostreams = NUM2INT(v_output); if(!NIL_P(v_input)) @@ -893,19 +1068,19 @@ initmsg.sinit_max_attempts = NUM2INT(v_attempts); if(!NIL_P(v_timeout)) initmsg.sinit_max_init_timeo = NUM2INT(v_timeout); - if(setsockopt(sock_fd, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg)) < 0) + if(setsockopt(fileno, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg)) < 0) rb_raise(rb_eSystemCallError, "setsockopt: %s", strerror(errno)); return self; } /* - * Subscribe to various notification types, which will generate additional - * data that the socket may receive. The possible notification types are + * Subscribe to various notification type events, which will generate additional + * data that the socket may receive. The possible notification type events are * as follows: * * :association * - A change has occurred to an association, either a new one has begun or an existing one has end. * @@ -917,39 +1092,36 @@ * * :shutdown * - The peer has sent a shutdown to the local endpoint. * * :data_io - * - Message data was received. On by default. + * - Message data was received. You will want to subscribe to this in most cases. * * Others: * * :adaptation * :authentication * :partial_delivery - * - * Not yet supported: - * * :sender_dry - * :peer_error + * :peer_error (aka remote error) * - * By default only data_io is subscribed to. - * * Example: * * socket = SCTP::Socket.new * * socket.bind(:port => port, :addresses => ['127.0.0.1']) - * socket.subscribe(:shutdown => true, :send_failure => true) + * socket.subscribe(:data_io => true, :shutdown => true, :send_failure => true) */ static VALUE rsctp_subscribe(VALUE self, VALUE v_options){ - int sock_fd; + int fileno; struct sctp_event_subscribe events; bzero(&events, sizeof(events)); - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); + Check_Type(v_options, T_HASH); + fileno = NUM2INT(rb_iv_get(self, "@fileno")); + if(RTEST(rb_hash_aref2(v_options, "data_io"))) events.sctp_data_io_event = 1; if(RTEST(rb_hash_aref2(v_options, "association"))) events.sctp_association_event = 1; @@ -980,83 +1152,117 @@ events.sctp_authentication_event = 1; if(RTEST(rb_hash_aref2(v_options, "sender_dry"))) events.sctp_sender_dry_event = 1; - if(setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events)) < 0) + if(setsockopt(fileno, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events)) < 0) rb_raise(rb_eSystemCallError, "setsockopt: %s", strerror(errno)); return self; } /* * Marks the socket referred to by sockfd as a passive socket, i.e. a socket that * will be used to accept incoming connection requests. * * The backlog argument defines the maximum length to which the queue of - * pending connections for sockfd may grow. The default is 1024. + * pending connections for sockfd may grow. The default value is 128. The + * maximum value is Socket::SOMAXCONN. * + * Why a default of 128? The answer is a "best guess" compromise between + * handling server load versus avoiding SYN flood attacks. I leave it as an + * exercise to the programmer to adjust as desired. + * + * See https://tangentsoft.com/wskfaq/advanced.html#backlog if you want + * more details on the advantages and drawbacks of various values. + * * Example: * * socket = SCTP::Socket.new * socket.bind(:port => 62534, :addresses => ['127.0.0.1']) * socket.listen - * */ static VALUE rsctp_listen(int argc, VALUE* argv, VALUE self){ VALUE v_backlog; - int backlog, sock_fd; + int backlog, fileno; rb_scan_args(argc, argv, "01", &v_backlog); if(NIL_P(v_backlog)) - backlog = 1024; + backlog = 128; else backlog = NUM2INT(v_backlog); - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); + if(backlog > SOMAXCONN) + rb_raise(rb_eArgError, "backlog value exceeds maximum value of: %i", SOMAXCONN); - if(listen(sock_fd, backlog) < 0) - rb_raise(rb_eSystemCallError, "setsockopt: %s", strerror(errno)); + fileno = NUM2INT(rb_iv_get(self, "@fileno")); + + if(listen(fileno, backlog) < 0) + rb_raise(rb_eSystemCallError, "listen: %s", strerror(errno)); return self; } /* * Extracts an association contained by a one-to-many socket connection into - * a one-to-one style socket. Note that this modifies the underlying sock_fd. + * a one-to-one style socket. Returns the socket descriptor (fileno). + * + * Example: + * + * socket = SCTP::Socket.new + * # etc... + * + * while true + * info = socket.recvmsg + * assoc_fileno = socket.peeloff(info.association_id) + * # ... Do something with this new fileno + * end */ static VALUE rsctp_peeloff(VALUE self, VALUE v_assoc_id){ - int sock_fd, new_sock_fd; + int fileno, assoc_fileno; sctp_assoc_t assoc_id; - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); + fileno = NUM2INT(rb_iv_get(self, "@fileno")); assoc_id = NUM2INT(v_assoc_id); - new_sock_fd = sctp_peeloff(sock_fd, assoc_id); + assoc_fileno = sctp_peeloff(fileno, assoc_id); - if(new_sock_fd < 0) + if(assoc_fileno < 0) rb_raise(rb_eSystemCallError, "sctp_peeloff: %s", strerror(errno)); - rb_iv_set(self, "@sock_fd", INT2NUM(new_sock_fd)); - - return self; + return INT2NUM(assoc_fileno); } +/* + * Returns the default set of parameters that a call to the sendto function + * uses on this association. This is a struct that contains the following + * members: + * + * * stream + * * ssn + * * flags + * * ppid + * * context + * * ttl + * * tsn + * * cumtsn + * * association_id + */ static VALUE rsctp_get_default_send_params(VALUE self){ - int sock_fd; + int fileno; socklen_t size; sctp_assoc_t assoc_id; struct sctp_sndrcvinfo sndrcv; bzero(&sndrcv, sizeof(sndrcv)); - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); + fileno = NUM2INT(rb_iv_get(self, "@fileno")); assoc_id = NUM2INT(rb_iv_get(self, "@association_id")); size = sizeof(struct sctp_sndrcvinfo); - if(sctp_opt_info(sock_fd, assoc_id, SCTP_DEFAULT_SEND_PARAM, (void*)&sndrcv, &size) < 0) + if(sctp_opt_info(fileno, assoc_id, SCTP_DEFAULT_SEND_PARAM, (void*)&sndrcv, &size) < 0) rb_raise(rb_eSystemCallError, "sctp_opt_info: %s", strerror(errno)); return rb_struct_new( v_sctp_default_send_params_struct, INT2NUM(sndrcv.sinfo_stream), @@ -1069,23 +1275,36 @@ INT2NUM(sndrcv.sinfo_cumtsn), INT2NUM(sndrcv.sinfo_assoc_id) ); } +/* + * Returns the association specific parameters. This is a struct + * that contains the following members: + * + * * association_id + * * max_retransmission_count + * * number_peer_destinations + * * peer_receive_window + * * local_receive_window + * * cookie_life + * + * All values that refer to time values are measured in milliseconds. + */ static VALUE rsctp_get_association_info(VALUE self){ - int sock_fd; + int fileno; socklen_t size; sctp_assoc_t assoc_id; struct sctp_assocparams assoc; bzero(&assoc, sizeof(assoc)); - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); + fileno = NUM2INT(rb_iv_get(self, "@fileno")); assoc_id = NUM2INT(rb_iv_get(self, "@association_id")); size = sizeof(struct sctp_assocparams); - if(sctp_opt_info(sock_fd, assoc_id, SCTP_ASSOCINFO, (void*)&assoc, &size) < 0) + if(sctp_opt_info(fileno, assoc_id, SCTP_ASSOCINFO, (void*)&assoc, &size) < 0) rb_raise(rb_eSystemCallError, "sctp_opt_info: %s", strerror(errno)); return rb_struct_new( v_sctp_associnfo_struct, INT2NUM(assoc.sasoc_assoc_id), @@ -1095,15 +1314,27 @@ INT2NUM(assoc.sasoc_local_rwnd), INT2NUM(assoc.sasoc_cookie_life) ); } +/* + * Shuts down socket send and receive operations. + * + * Optionally accepts an argument that specifieds the type of shutdown. + * This can be one of the following values: + * + * * SHUT_RD - Disables further receive operations. + * * SHUT_WR - Disables further send operations. + * * SHUT_RDWR - Disables further send and receive operations. + * + * The default is SHUT_RDWR. + */ static VALUE rsctp_shutdown(int argc, VALUE* argv, VALUE self){ - int how, sock_fd; + int how, fileno; VALUE v_how; - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); + fileno = NUM2INT(rb_iv_get(self, "@fileno")); rb_scan_args(argc, argv, "01", &v_how); if(NIL_P(v_how)){ how = SHUT_RDWR; @@ -1111,29 +1342,39 @@ else{ Check_Type(v_how, T_FIXNUM); how = NUM2INT(v_how); } - if(shutdown(sock_fd, how) < 0) + if(shutdown(fileno, how) < 0) rb_raise(rb_eSystemCallError, "shutdown: %s", strerror(errno)); return self; } +/* + * Returns the protocol parameters that are used to initialize and bind the + * retransmission timeout (RTO) tunable. This is a struct with the following + * members: + * + * * association_id + * * initial + * * max + * * min + */ static VALUE rsctp_get_retransmission_info(VALUE self){ - int sock_fd; + int fileno; socklen_t size; sctp_assoc_t assoc_id; struct sctp_rtoinfo rto; bzero(&rto, sizeof(rto)); - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); + fileno = NUM2INT(rb_iv_get(self, "@fileno")); assoc_id = NUM2INT(rb_iv_get(self, "@association_id")); size = sizeof(struct sctp_rtoinfo); - if(sctp_opt_info(sock_fd, assoc_id, SCTP_RTOINFO, (void*)&rto, &size) < 0) + if(sctp_opt_info(fileno, assoc_id, SCTP_RTOINFO, (void*)&rto, &size) < 0) rb_raise(rb_eSystemCallError, "sctp_opt_info: %s", strerror(errno)); return rb_struct_new( v_sctp_rtoinfo_struct, INT2NUM(rto.srto_assoc_id), @@ -1141,25 +1382,46 @@ INT2NUM(rto.srto_max), INT2NUM(rto.srto_min) ); } +/* + * Get the status of a connected socket. + * + * Example: + * + * socket = SCTP::Socket.new + * socket.connect(...) + * socket.get_status + * + * Returns a Struct::Status object, which contains the following fields: + * + * * association_id + * * state + * * receive_window + * * unacknowledged_data + * * pending_data + * * inbound_streams + * * outbound_streams + * * fragmentation_point + * * primary (IP) + */ static VALUE rsctp_get_status(VALUE self){ - int sock_fd; + int fileno; socklen_t size; sctp_assoc_t assoc_id; struct sctp_status status; struct sctp_paddrinfo* spinfo; char tmpname[INET_ADDRSTRLEN]; bzero(&status, sizeof(status)); - sock_fd = NUM2INT(rb_iv_get(self, "@sock_fd")); + fileno = NUM2INT(rb_iv_get(self, "@fileno")); assoc_id = NUM2INT(rb_iv_get(self, "@association_id")); size = sizeof(struct sctp_status); - if(sctp_opt_info(sock_fd, assoc_id, SCTP_STATUS, (void*)&status, &size) < 0) + if(sctp_opt_info(fileno, assoc_id, SCTP_STATUS, (void*)&status, &size) < 0) rb_raise(rb_eSystemCallError, "sctp_opt_info: %s", strerror(errno)); spinfo = &status.sstat_primary; if (spinfo->spinfo_address.ss_family == AF_INET6) { @@ -1184,11 +1446,92 @@ INT2NUM(status.sstat_fragmentation_point), rb_str_new2(tmpname) ); } -void Init_socket(){ +/* + * Returns a struct of events detailing which events have been + * subscribed to by the socket. The struct contains the following + * members: + * + * * data_io + * * association + * * address + * * send_failure + * * peer_error + * * shutdown + * * partial_delivery + * * adaptation_layer + * * authentication + * * sender_dry + * * stream_reset + * * assoc_reset + * * stream_change + * * send_failure_event + */ +static VALUE rsctp_get_subscriptions(VALUE self){ + int fileno; + socklen_t size; + sctp_assoc_t assoc_id; + struct sctp_event_subscribe events; + + bzero(&events, sizeof(events)); + + fileno = NUM2INT(rb_iv_get(self, "@fileno")); + assoc_id = NUM2INT(rb_iv_get(self, "@association_id")); + size = sizeof(struct sctp_event_subscribe); + + if(sctp_opt_info(fileno, assoc_id, SCTP_EVENTS, (void*)&events, &size) < 0) + rb_raise(rb_eSystemCallError, "sctp_opt_info: %s", strerror(errno)); + + return rb_struct_new(v_sctp_event_subscribe_struct, + (events.sctp_data_io_event ? Qtrue : Qfalse), + (events.sctp_association_event ? Qtrue : Qfalse), + (events.sctp_address_event ? Qtrue : Qfalse), + (events.sctp_send_failure_event ? Qtrue : Qfalse), + (events.sctp_peer_error_event ? Qtrue : Qfalse), + (events.sctp_shutdown_event ? Qtrue : Qfalse), + (events.sctp_partial_delivery_event ? Qtrue : Qfalse), + (events.sctp_adaptation_layer_event ? Qtrue : Qfalse), + (events.sctp_authentication_event ? Qtrue : Qfalse), + (events.sctp_sender_dry_event ? Qtrue : Qfalse), + (events.sctp_stream_reset_event ? Qtrue : Qfalse), + (events.sctp_assoc_reset_event ? Qtrue : Qfalse), + (events.sctp_stream_change_event ? Qtrue : Qfalse), + (events.sctp_send_failure_event_event ? Qtrue : Qfalse) + ); +} + +static VALUE rsctp_get_peer_address_params(VALUE self){ + int fileno; + char str[16]; + socklen_t size; + sctp_assoc_t assoc_id; + struct sctp_paddrparams paddr; + + bzero(&paddr, sizeof(paddr)); + bzero(&str, sizeof(str)); + + fileno = NUM2INT(rb_iv_get(self, "@fileno")); + assoc_id = NUM2INT(rb_iv_get(self, "@association_id")); + size = sizeof(struct sctp_paddrparams); + + if(sctp_opt_info(fileno, assoc_id, SCTP_PEER_ADDR_PARAMS, (void*)&paddr, &size) < 0) + rb_raise(rb_eSystemCallError, "sctp_opt_info: %s", strerror(errno)); + + inet_ntop(AF_INET, ((struct sockaddr_in*)&paddr.spp_address), str, sizeof(str)); + + return rb_struct_new( + v_sctp_peer_addr_params_struct, + INT2NUM(paddr.spp_assoc_id), + rb_str_new2(str), + INT2NUM(paddr.spp_hbinterval), + INT2NUM(paddr.spp_pathmaxrxt) + ); +} + +void Init_socket(void){ mSCTP = rb_define_module("SCTP"); cSocket = rb_define_class_under(mSCTP, "Socket", rb_cObject); v_sndrcv_struct = rb_struct_define( "SendReceiveInfo", "message", "stream", "flags", @@ -1204,11 +1547,11 @@ "PeerAddrChange", "type", "length", "ip_address", "state", "error", "association_id", "info", NULL ); v_remote_error_struct = rb_struct_define( - "RemoteError", "type", "length", "error", "association_id", "data", NULL + "RemoteError", "type", "flags", "length", "error", "association_id", "data", NULL ); v_send_failed_event_struct = rb_struct_define( "SendFailedEvent", "type", "length", "error", "association_id", "data", NULL ); @@ -1232,10 +1575,14 @@ v_auth_event_struct = rb_struct_define( "AuthEvent", "type", "length", "key_number", "indication", "association_id", NULL ); + v_sender_dry_event_struct = rb_struct_define( + "SenderDryEvent", "type", "flags", "length", "association_id", NULL + ); + v_sockaddr_in_struct = rb_struct_define( "SockAddrIn", "family", "port", "address", NULL ); v_sctp_status_struct = rb_struct_define( @@ -1256,43 +1603,66 @@ v_sctp_default_send_params_struct = rb_struct_define( "DefaultSendParams", "stream", "ssn", "flags", "ppid", "context", "ttl", "tsn", "cumtsn", "association_id", NULL ); + v_sctp_event_subscribe_struct = rb_struct_define( + "EventSubscriptions", "data_io", "association", "address", "send_failure", + "peer_error", "shutdown", "partial_delivery", "adaptation_layer", + "authentication", "sender_dry", "stream_reset", "assoc_reset", + "stream_change", "send_failure_event", NULL + ); + + v_sctp_receive_info_struct = rb_struct_define( + "ReceiveInfo", "message", "sid", "ssn", "flags", "ppid", "tsn", + "cumtsn", "context", "assocation_id", NULL + ); + + v_sctp_peer_addr_params_struct = rb_struct_define( + "PeerAddressParams", "association_id", "address", "heartbeat_interval", + "max_retransmission_count", NULL + ); + rb_define_method(cSocket, "initialize", rsctp_init, -1); - rb_define_method(cSocket, "bind", rsctp_bind, -1); + rb_define_method(cSocket, "bindx", rsctp_bindx, -1); rb_define_method(cSocket, "close", rsctp_close, 0); - rb_define_method(cSocket, "connect", rsctp_connect, -1); - rb_define_method(cSocket, "getpeernames", rsctp_getpeernames, 0); - rb_define_method(cSocket, "getlocalnames", rsctp_getlocalnames, 0); + rb_define_method(cSocket, "connectx", rsctp_connectx, -1); + rb_define_method(cSocket, "getpeernames", rsctp_getpeernames, -1); + rb_define_method(cSocket, "getlocalnames", rsctp_getlocalnames, -1); rb_define_method(cSocket, "get_status", rsctp_get_status, 0); rb_define_method(cSocket, "get_default_send_params", rsctp_get_default_send_params, 0); rb_define_method(cSocket, "get_retransmission_info", rsctp_get_retransmission_info, 0); rb_define_method(cSocket, "get_association_info", rsctp_get_association_info, 0); + rb_define_method(cSocket, "get_subscriptions", rsctp_get_subscriptions, 0); + rb_define_method(cSocket, "get_peer_address_params", rsctp_get_peer_address_params, 0); rb_define_method(cSocket, "listen", rsctp_listen, -1); - rb_define_method(cSocket, "peeloff!", rsctp_peeloff, 1); + rb_define_method(cSocket, "peeloff", rsctp_peeloff, 1); rb_define_method(cSocket, "recvmsg", rsctp_recvmsg, -1); rb_define_method(cSocket, "send", rsctp_send, 1); #ifdef HAVE_SCTP_SENDV rb_define_method(cSocket, "sendv", rsctp_sendv, 1); #endif +#ifdef HAVE_SCTP_RECVV + rb_define_method(cSocket, "recvv", rsctp_recvv, -1); +#endif + rb_define_method(cSocket, "sendmsg", rsctp_sendmsg, 1); rb_define_method(cSocket, "set_initmsg", rsctp_set_initmsg, 1); rb_define_method(cSocket, "shutdown", rsctp_shutdown, -1); rb_define_method(cSocket, "subscribe", rsctp_subscribe, 1); rb_define_attr(cSocket, "domain", 1, 1); rb_define_attr(cSocket, "type", 1, 1); - rb_define_attr(cSocket, "sock_fd", 1, 1); + rb_define_attr(cSocket, "fileno", 1, 1); rb_define_attr(cSocket, "association_id", 1, 1); rb_define_attr(cSocket, "port", 1, 1); - /* 0.0.5: The version of this library */ - rb_define_const(cSocket, "VERSION", rb_str_new2("0.0.5")); + /* 0.1.0: The version of this library */ + rb_define_const(cSocket, "VERSION", rb_str_new2("0.1.0")); /* send flags */ /* Message is unordered */ rb_define_const(cSocket, "SCTP_UNORDERED", INT2NUM(SCTP_UNORDERED)); @@ -1320,6 +1690,11 @@ rb_define_const(cSocket, "SCTP_ESTABLISHED", INT2NUM(SCTP_ESTABLISHED)); rb_define_const(cSocket, "SCTP_SHUTDOWN_PENDING", INT2NUM(SCTP_SHUTDOWN_PENDING)); rb_define_const(cSocket, "SCTP_SHUTDOWN_SENT", INT2NUM(SCTP_SHUTDOWN_SENT)); rb_define_const(cSocket, "SCTP_SHUTDOWN_RECEIVED", INT2NUM(SCTP_SHUTDOWN_RECEIVED)); rb_define_const(cSocket, "SCTP_SHUTDOWN_ACK_SENT", INT2NUM(SCTP_SHUTDOWN_ACK_SENT)); + + // BINDING // + + rb_define_const(cSocket, "SCTP_BINDX_ADD_ADDR", INT2NUM(SCTP_BINDX_ADD_ADDR)); + rb_define_const(cSocket, "SCTP_BINDX_REM_ADDR", INT2NUM(SCTP_BINDX_REM_ADDR)); }