#include <linux/types.h>
#include <asm/byteorder.h>
-/* Structure describing an Internet (DCCP) socket address. */
-struct sockaddr_dccp {
- __u16 sdccp_family; /* Address family */
- __u16 sdccp_port; /* Port number */
- __u32 sdccp_addr; /* Internet address */
- __u32 sdccp_service; /* Service */
- /* Pad to size of `struct sockaddr': 16 bytes . */
- __u32 sdccp_pad;
-};
-
/**
* struct dccp_hdr - generic part of DCCP packet header
*
/* DCCP socket options */
#define DCCP_SOCKOPT_PACKET_SIZE 1
+#define DCCP_SOCKOPT_SERVICE 2
+
+#define DCCP_SERVICE_LIST_MAX_LEN 32
#ifdef __KERNEL__
DCCP_ROLE_SERVER,
};
+struct dccp_service_list {
+ __u32 dccpsl_nr;
+ __u32 dccpsl_list[0];
+};
+
+#define DCCP_SERVICE_INVALID_VALUE htonl((__u32)-1)
+
+static inline int dccp_list_has_service(const struct dccp_service_list *sl,
+ const u32 service)
+{
+ if (likely(sl != NULL)) {
+ u32 i = sl->dccpsl_nr;
+ while (i--)
+ if (sl->dccpsl_list[i] == service)
+ return 1;
+ }
+ return 0;
+}
+
/**
* struct dccp_sock - DCCP socket state
*
__u64 dccps_gss;
__u64 dccps_gsr;
__u64 dccps_gar;
- unsigned long dccps_service;
+ __u32 dccps_service;
+ struct dccp_service_list *dccps_service_list;
struct timeval dccps_timestamp_time;
__u32 dccps_timestamp_echo;
__u32 dccps_packet_size;
return (struct dccp_sock *)sk;
}
+static inline int dccp_service_not_initialized(const struct sock *sk)
+{
+ return dccp_sk(sk)->dccps_service == DCCP_SERVICE_INVALID_VALUE;
+}
+
static inline const char *dccp_role(const struct sock *sk)
{
switch (dccp_sk(sk)->dccps_role) {
extern void dccp_send_close(struct sock *sk, const int active);
struct dccp_skb_cb {
- __u8 dccpd_type;
- __u8 dccpd_reset_code;
- __u8 dccpd_service;
- __u8 dccpd_ccval;
+ __u8 dccpd_type:4;
+ __u8 dccpd_ccval:4;
+ __u8 dccpd_reset_code;
+ __u16 dccpd_opt_len;
__u64 dccpd_seq;
__u64 dccpd_ack_seq;
- int dccpd_opt_len;
};
#define DCCP_SKB_CB(__skb) ((struct dccp_skb_cb *)&((__skb)->cb[0]))
dp->dccps_role = DCCP_ROLE_CLIENT;
+ if (dccp_service_not_initialized(sk))
+ return -EPROTO;
+
if (addr_len < sizeof(struct sockaddr_in))
return -EINVAL;
dccp_hdr(skb)->dccph_sport);
}
+static inline int dccp_bad_service_code(const struct sock *sk,
+ const __u32 service)
+{
+ const struct dccp_sock *dp = dccp_sk(sk);
+
+ if (dp->dccps_service == service)
+ return 0;
+ return !dccp_list_has_service(dp->dccps_service_list, service);
+}
+
int dccp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
struct inet_request_sock *ireq;
struct dccp_request_sock *dreq;
const __u32 saddr = skb->nh.iph->saddr;
const __u32 daddr = skb->nh.iph->daddr;
+ const __u32 service = dccp_hdr_request(skb)->dccph_req_service;
struct dccp_skb_cb *dcb = DCCP_SKB_CB(skb);
__u8 reset_code = DCCP_RESET_CODE_TOO_BUSY;
struct dst_entry *dst = NULL;
goto drop;
}
+ if (dccp_bad_service_code(sk, service)) {
+ reset_code = DCCP_RESET_CODE_BAD_SERVICE_CODE;
+ goto drop;
+ }
/*
* TW buckets are converted to open requests without
* limitations, they conserve resources and peer is
* dccp_create_openreq_child.
*/
dreq = dccp_rsk(req);
- dreq->dreq_isr = dcb->dccpd_seq;
- dreq->dreq_iss = dccp_v4_init_sequence(sk, skb);
- dreq->dreq_service = dccp_hdr_request(skb)->dccph_req_service;
+ dreq->dreq_isr = dcb->dccpd_seq;
+ dreq->dreq_iss = dccp_v4_init_sequence(sk, skb);
+ dreq->dreq_service = service;
if (dccp_v4_send_response(sk, req, dst))
goto drop_and_free;
sk->sk_write_space = dccp_write_space;
dp->dccps_mss_cache = 536;
dp->dccps_role = DCCP_ROLE_UNDEFINED;
+ dp->dccps_service = DCCP_SERVICE_INVALID_VALUE;
return 0;
}
if (inet_csk(sk)->icsk_bind_hash != NULL)
inet_put_port(&dccp_hashinfo, sk);
+ if (dp->dccps_service_list != NULL) {
+ kfree(dp->dccps_service_list);
+ dp->dccps_service_list = NULL;
+ }
+
ccid_hc_rx_exit(dp->dccps_hc_rx_ccid, sk);
ccid_hc_tx_exit(dp->dccps_hc_tx_ccid, sk);
dccp_ackpkts_free(dp->dccps_hc_rx_ackpkts);
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_role = DCCP_ROLE_SERVER;
- newicsk->icsk_rto = DCCP_TIMEOUT_INIT;
+ 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) {
switch (dcb->dccpd_type) {
case DCCP_PKT_REQUEST:
dccp_hdr_request(skb)->dccph_req_service =
- dcb->dccpd_service;
+ dp->dccps_service;
break;
case DCCP_PKT_RESET:
dccp_hdr_reset(skb)->dccph_reset_code =
struct request_sock *req)
{
struct dccp_hdr *dh;
+ struct dccp_request_sock *dreq;
const int dccp_header_size = sizeof(struct dccp_hdr) +
sizeof(struct dccp_hdr_ext) +
sizeof(struct dccp_hdr_response);
skb->dst = dst_clone(dst);
skb->csum = 0;
+ dreq = dccp_rsk(req);
DCCP_SKB_CB(skb)->dccpd_type = DCCP_PKT_RESPONSE;
- DCCP_SKB_CB(skb)->dccpd_seq = dccp_rsk(req)->dreq_iss;
+ DCCP_SKB_CB(skb)->dccpd_seq = dreq->dreq_iss;
dccp_insert_options(sk, skb);
skb->h.raw = skb_push(skb, dccp_header_size);
DCCP_SKB_CB(skb)->dccpd_opt_len) / 4;
dh->dccph_type = DCCP_PKT_RESPONSE;
dh->dccph_x = 1;
- dccp_hdr_set_seq(dh, dccp_rsk(req)->dreq_iss);
- dccp_hdr_set_ack(dccp_hdr_ack_bits(skb), dccp_rsk(req)->dreq_isr);
+ dccp_hdr_set_seq(dh, dreq->dreq_iss);
+ dccp_hdr_set_ack(dccp_hdr_ack_bits(skb), dreq->dreq_isr);
+ dccp_hdr_response(skb)->dccph_resp_service = dreq->dreq_service;
dh->dccph_checksum = dccp_v4_checksum(skb, inet_rsk(req)->loc_addr,
inet_rsk(req)->rmt_addr);
skb_reserve(skb, MAX_DCCP_HEADER);
DCCP_SKB_CB(skb)->dccpd_type = DCCP_PKT_REQUEST;
- /* FIXME: set service to something meaningful, coming
- * from userspace*/
- DCCP_SKB_CB(skb)->dccpd_service = 0;
skb->csum = 0;
skb_set_owner_w(skb, sk);
static inline int dccp_listen_start(struct sock *sk)
{
- dccp_sk(sk)->dccps_role = DCCP_ROLE_LISTEN;
+ struct dccp_sock *dp = dccp_sk(sk);
+
+ dp->dccps_role = DCCP_ROLE_LISTEN;
+ /*
+ * Apps need to use setsockopt(DCCP_SOCKOPT_SERVICE)
+ * before calling listen()
+ */
+ if (dccp_service_not_initialized(sk))
+ return -EPROTO;
return inet_csk_listen_start(sk, TCP_SYNQ_HSIZE);
}
return -ENOIOCTLCMD;
}
+static int dccp_setsockopt_service(struct sock *sk, const u32 service,
+ char __user *optval, int optlen)
+{
+ struct dccp_sock *dp = dccp_sk(sk);
+ struct dccp_service_list *sl = NULL;
+
+ if (service == DCCP_SERVICE_INVALID_VALUE ||
+ optlen > DCCP_SERVICE_LIST_MAX_LEN * sizeof(u32))
+ return -EINVAL;
+
+ if (optlen > sizeof(service)) {
+ sl = kmalloc(optlen, GFP_KERNEL);
+ if (sl == NULL)
+ return -ENOMEM;
+
+ sl->dccpsl_nr = optlen / sizeof(u32) - 1;
+ if (copy_from_user(sl->dccpsl_list,
+ optval + sizeof(service),
+ optlen - sizeof(service)) ||
+ dccp_list_has_service(sl, DCCP_SERVICE_INVALID_VALUE)) {
+ kfree(sl);
+ return -EFAULT;
+ }
+ }
+
+ lock_sock(sk);
+ dp->dccps_service = service;
+
+ if (dp->dccps_service_list != NULL)
+ kfree(dp->dccps_service_list);
+
+ dp->dccps_service_list = sl;
+ release_sock(sk);
+ return 0;
+}
+
int dccp_setsockopt(struct sock *sk, int level, int optname,
char __user *optval, int optlen)
{
if (get_user(val, (int __user *)optval))
return -EFAULT;
- lock_sock(sk);
+ if (optname == DCCP_SOCKOPT_SERVICE)
+ return dccp_setsockopt_service(sk, val, optval, optlen);
+ lock_sock(sk);
dp = dccp_sk(sk);
err = 0;
return err;
}
+static int dccp_getsockopt_service(struct sock *sk, int len,
+ u32 __user *optval,
+ int __user *optlen)
+{
+ const struct dccp_sock *dp = dccp_sk(sk);
+ const struct dccp_service_list *sl;
+ int err = -ENOENT, slen = 0, total_len = sizeof(u32);
+
+ lock_sock(sk);
+ if (dccp_service_not_initialized(sk))
+ goto out;
+
+ if ((sl = dp->dccps_service_list) != NULL) {
+ slen = sl->dccpsl_nr * sizeof(u32);
+ total_len += slen;
+ }
+
+ err = -EINVAL;
+ if (total_len > len)
+ goto out;
+
+ err = 0;
+ if (put_user(total_len, optlen) ||
+ put_user(dp->dccps_service, optval) ||
+ (sl != NULL && copy_to_user(optval + 1, sl->dccpsl_list, slen)))
+ err = -EFAULT;
+out:
+ release_sock(sk);
+ return err;
+}
+
int dccp_getsockopt(struct sock *sk, int level, int optname,
char __user *optval, int __user *optlen)
{
if (get_user(len, optlen))
return -EFAULT;
+ if (optname == DCCP_SOCKOPT_SERVICE)
+ return dccp_getsockopt_service(sk, len,
+ (u32 __user *)optval, optlen);
+
len = min_t(unsigned int, len, sizeof(int));
if (len < 0)
return -EINVAL;