mirror of
https://github.com/FEX-Emu/linux.git
synced 2024-12-18 06:50:08 +00:00
67e6b62921
As discussed in the dccp@vger mailing list: Now applications have to use setsockopt(DCCP_SOCKOPT_SERVICE, service[s]), prior to calling listen() and connect(). An array of unsigned ints can be passed meaning that the listening sock accepts connection requests for several services. With this we can ditch struct sockaddr_dccp and use only sockaddr_in (and sockaddr_in6 in the future). Signed-off-by: Arnaldo Carvalho de Melo <acme@mandriva.com> Signed-off-by: David S. Miller <davem@davemloft.net>
268 lines
7.4 KiB
C
268 lines
7.4 KiB
C
/*
|
|
* net/dccp/minisocks.c
|
|
*
|
|
* An implementation of the DCCP protocol
|
|
* Arnaldo Carvalho de Melo <acme@conectiva.com.br>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*/
|
|
|
|
#include <linux/config.h>
|
|
#include <linux/dccp.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/timer.h>
|
|
|
|
#include <net/sock.h>
|
|
#include <net/xfrm.h>
|
|
#include <net/inet_timewait_sock.h>
|
|
|
|
#include "ccid.h"
|
|
#include "dccp.h"
|
|
|
|
struct inet_timewait_death_row dccp_death_row = {
|
|
.sysctl_max_tw_buckets = NR_FILE * 2,
|
|
.period = DCCP_TIMEWAIT_LEN / INET_TWDR_TWKILL_SLOTS,
|
|
.death_lock = SPIN_LOCK_UNLOCKED,
|
|
.hashinfo = &dccp_hashinfo,
|
|
.tw_timer = TIMER_INITIALIZER(inet_twdr_hangman, 0,
|
|
(unsigned long)&dccp_death_row),
|
|
.twkill_work = __WORK_INITIALIZER(dccp_death_row.twkill_work,
|
|
inet_twdr_twkill_work,
|
|
&dccp_death_row),
|
|
/* Short-time timewait calendar */
|
|
|
|
.twcal_hand = -1,
|
|
.twcal_timer = TIMER_INITIALIZER(inet_twdr_twcal_tick, 0,
|
|
(unsigned long)&dccp_death_row),
|
|
};
|
|
|
|
void dccp_time_wait(struct sock *sk, int state, int timeo)
|
|
{
|
|
struct inet_timewait_sock *tw = NULL;
|
|
|
|
if (dccp_death_row.tw_count < dccp_death_row.sysctl_max_tw_buckets)
|
|
tw = inet_twsk_alloc(sk, state);
|
|
|
|
if (tw != NULL) {
|
|
const struct inet_connection_sock *icsk = inet_csk(sk);
|
|
const int rto = (icsk->icsk_rto << 2) - (icsk->icsk_rto >> 1);
|
|
|
|
/* Linkage updates. */
|
|
__inet_twsk_hashdance(tw, sk, &dccp_hashinfo);
|
|
|
|
/* Get the TIME_WAIT timeout firing. */
|
|
if (timeo < rto)
|
|
timeo = rto;
|
|
|
|
tw->tw_timeout = DCCP_TIMEWAIT_LEN;
|
|
if (state == DCCP_TIME_WAIT)
|
|
timeo = DCCP_TIMEWAIT_LEN;
|
|
|
|
inet_twsk_schedule(tw, &dccp_death_row, timeo,
|
|
DCCP_TIMEWAIT_LEN);
|
|
inet_twsk_put(tw);
|
|
} else {
|
|
/* Sorry, if we're out of memory, just CLOSE this
|
|
* socket up. We've got bigger problems than
|
|
* non-graceful socket closings.
|
|
*/
|
|
LIMIT_NETDEBUG(KERN_INFO "DCCP: time wait bucket "
|
|
"table overflow\n");
|
|
}
|
|
|
|
dccp_done(sk);
|
|
}
|
|
|
|
struct sock *dccp_create_openreq_child(struct sock *sk,
|
|
const struct request_sock *req,
|
|
const struct sk_buff *skb)
|
|
{
|
|
/*
|
|
* Step 3: Process LISTEN state
|
|
*
|
|
* // Generate a new socket and switch to that socket
|
|
* Set S := new socket for this port pair
|
|
*/
|
|
struct sock *newsk = inet_csk_clone(sk, req, GFP_ATOMIC);
|
|
|
|
if (newsk != NULL) {
|
|
const struct dccp_request_sock *dreq = dccp_rsk(req);
|
|
struct inet_connection_sock *newicsk = inet_csk(sk);
|
|
struct dccp_sock *newdp = dccp_sk(newsk);
|
|
|
|
newdp->dccps_role = DCCP_ROLE_SERVER;
|
|
newdp->dccps_hc_rx_ackpkts = NULL;
|
|
newdp->dccps_service_list = NULL;
|
|
newdp->dccps_service = dreq->dreq_service;
|
|
newicsk->icsk_rto = DCCP_TIMEOUT_INIT;
|
|
do_gettimeofday(&newdp->dccps_epoch);
|
|
|
|
if (newdp->dccps_options.dccpo_send_ack_vector) {
|
|
newdp->dccps_hc_rx_ackpkts =
|
|
dccp_ackpkts_alloc(DCCP_MAX_ACK_VECTOR_LEN,
|
|
GFP_ATOMIC);
|
|
/*
|
|
* XXX: We're using the same CCIDs set on the parent,
|
|
* i.e. sk_clone copied the master sock and left the
|
|
* CCID pointers for this child, that is why we do the
|
|
* __ccid_get calls.
|
|
*/
|
|
if (unlikely(newdp->dccps_hc_rx_ackpkts == NULL))
|
|
goto out_free;
|
|
}
|
|
|
|
if (unlikely(ccid_hc_rx_init(newdp->dccps_hc_rx_ccid,
|
|
newsk) != 0 ||
|
|
ccid_hc_tx_init(newdp->dccps_hc_tx_ccid,
|
|
newsk) != 0)) {
|
|
dccp_ackpkts_free(newdp->dccps_hc_rx_ackpkts);
|
|
ccid_hc_rx_exit(newdp->dccps_hc_rx_ccid, newsk);
|
|
ccid_hc_tx_exit(newdp->dccps_hc_tx_ccid, newsk);
|
|
out_free:
|
|
/* It is still raw copy of parent, so invalidate
|
|
* destructor and make plain sk_free() */
|
|
newsk->sk_destruct = NULL;
|
|
sk_free(newsk);
|
|
return NULL;
|
|
}
|
|
|
|
__ccid_get(newdp->dccps_hc_rx_ccid);
|
|
__ccid_get(newdp->dccps_hc_tx_ccid);
|
|
|
|
/*
|
|
* Step 3: Process LISTEN state
|
|
*
|
|
* Choose S.ISS (initial seqno) or set from Init Cookie
|
|
* Set S.ISR, S.GSR, S.SWL, S.SWH from packet or Init
|
|
* Cookie
|
|
*/
|
|
|
|
/* See dccp_v4_conn_request */
|
|
newdp->dccps_options.dccpo_sequence_window = req->rcv_wnd;
|
|
|
|
newdp->dccps_gar = newdp->dccps_isr = dreq->dreq_isr;
|
|
dccp_update_gsr(newsk, dreq->dreq_isr);
|
|
|
|
newdp->dccps_iss = dreq->dreq_iss;
|
|
dccp_update_gss(newsk, dreq->dreq_iss);
|
|
|
|
/*
|
|
* SWL and AWL are initially adjusted so that they are not less than
|
|
* the initial Sequence Numbers received and sent, respectively:
|
|
* SWL := max(GSR + 1 - floor(W/4), ISR),
|
|
* AWL := max(GSS - W' + 1, ISS).
|
|
* These adjustments MUST be applied only at the beginning of the
|
|
* connection.
|
|
*/
|
|
dccp_set_seqno(&newdp->dccps_swl,
|
|
max48(newdp->dccps_swl, newdp->dccps_isr));
|
|
dccp_set_seqno(&newdp->dccps_awl,
|
|
max48(newdp->dccps_awl, newdp->dccps_iss));
|
|
|
|
dccp_init_xmit_timers(newsk);
|
|
|
|
DCCP_INC_STATS_BH(DCCP_MIB_PASSIVEOPENS);
|
|
}
|
|
return newsk;
|
|
}
|
|
|
|
/*
|
|
* Process an incoming packet for RESPOND sockets represented
|
|
* as an request_sock.
|
|
*/
|
|
struct sock *dccp_check_req(struct sock *sk, struct sk_buff *skb,
|
|
struct request_sock *req,
|
|
struct request_sock **prev)
|
|
{
|
|
struct sock *child = NULL;
|
|
|
|
/* Check for retransmitted REQUEST */
|
|
if (dccp_hdr(skb)->dccph_type == DCCP_PKT_REQUEST) {
|
|
if (after48(DCCP_SKB_CB(skb)->dccpd_seq,
|
|
dccp_rsk(req)->dreq_isr)) {
|
|
struct dccp_request_sock *dreq = dccp_rsk(req);
|
|
|
|
dccp_pr_debug("Retransmitted REQUEST\n");
|
|
/* Send another RESPONSE packet */
|
|
dccp_set_seqno(&dreq->dreq_iss, dreq->dreq_iss + 1);
|
|
dccp_set_seqno(&dreq->dreq_isr,
|
|
DCCP_SKB_CB(skb)->dccpd_seq);
|
|
req->rsk_ops->rtx_syn_ack(sk, req, NULL);
|
|
}
|
|
/* Network Duplicate, discard packet */
|
|
return NULL;
|
|
}
|
|
|
|
DCCP_SKB_CB(skb)->dccpd_reset_code = DCCP_RESET_CODE_PACKET_ERROR;
|
|
|
|
if (dccp_hdr(skb)->dccph_type != DCCP_PKT_ACK &&
|
|
dccp_hdr(skb)->dccph_type != DCCP_PKT_DATAACK)
|
|
goto drop;
|
|
|
|
/* Invalid ACK */
|
|
if (DCCP_SKB_CB(skb)->dccpd_ack_seq != dccp_rsk(req)->dreq_iss) {
|
|
dccp_pr_debug("Invalid ACK number: ack_seq=%llu, "
|
|
"dreq_iss=%llu\n",
|
|
(unsigned long long)
|
|
DCCP_SKB_CB(skb)->dccpd_ack_seq,
|
|
(unsigned long long)
|
|
dccp_rsk(req)->dreq_iss);
|
|
goto drop;
|
|
}
|
|
|
|
child = dccp_v4_request_recv_sock(sk, skb, req, NULL);
|
|
if (child == NULL)
|
|
goto listen_overflow;
|
|
|
|
/* FIXME: deal with options */
|
|
|
|
inet_csk_reqsk_queue_unlink(sk, req, prev);
|
|
inet_csk_reqsk_queue_removed(sk, req);
|
|
inet_csk_reqsk_queue_add(sk, req, child);
|
|
out:
|
|
return child;
|
|
listen_overflow:
|
|
dccp_pr_debug("listen_overflow!\n");
|
|
DCCP_SKB_CB(skb)->dccpd_reset_code = DCCP_RESET_CODE_TOO_BUSY;
|
|
drop:
|
|
if (dccp_hdr(skb)->dccph_type != DCCP_PKT_RESET)
|
|
req->rsk_ops->send_reset(skb);
|
|
|
|
inet_csk_reqsk_queue_drop(sk, req, prev);
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Queue segment on the new socket if the new socket is active,
|
|
* otherwise we just shortcircuit this and continue with
|
|
* the new socket.
|
|
*/
|
|
int dccp_child_process(struct sock *parent, struct sock *child,
|
|
struct sk_buff *skb)
|
|
{
|
|
int ret = 0;
|
|
const int state = child->sk_state;
|
|
|
|
if (!sock_owned_by_user(child)) {
|
|
ret = dccp_rcv_state_process(child, skb, dccp_hdr(skb),
|
|
skb->len);
|
|
|
|
/* Wakeup parent, send SIGIO */
|
|
if (state == DCCP_RESPOND && child->sk_state != state)
|
|
parent->sk_data_ready(parent, 0);
|
|
} else {
|
|
/* Alas, it is possible again, because we do lookup
|
|
* in main socket hash table and lock on listening
|
|
* socket does not protect us more.
|
|
*/
|
|
sk_add_backlog(child, skb);
|
|
}
|
|
|
|
bh_unlock_sock(child);
|
|
sock_put(child);
|
|
return ret;
|
|
}
|