[V7,2/4] sctp: Add ip option support
diff mbox

Message ID 20180220191527.14307-1-richard_c_haines@btinternet.com
State New
Headers show

Commit Message

Richard Haines Feb. 20, 2018, 7:15 p.m. UTC
Add ip option support to allow LSM security modules to utilise CIPSO/IPv4
and CALIPSO/IPv6 services.

Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
---
All SCTP lksctp-tools/src/func_tests run correctly in enforcing mode.
All "./sctp-tests run" obtained from: https://github.com/sctp/sctp-tests
pass.

V7 Changes:
1) Log when copy ip options fail for IPv4 and IPv6
2) Correct sctp_setsockopt_maxseg() function. Note that the lksctp-tools
func_tests do not test with struct sctp_assoc_value. Just used simple test
and okay.
3) Move calculation of overheads to sctp_packet_config().
NOTE: Initially in sctp_packet_reset() I set packet->size and
packet->overhead to zero (as it is a reset). This was okay for all the
lksctp-tools function tests, however when running "sctp-tests" ndatshched
tests it causes these to fail with an st_s.log entry of:
	sid: 3, expected: 3
	sid: 3, expected: 3
	unexpected sid packet !!!
	sid: 1, expected: 3

I then found sctp_packet_transmit() relies on setting
"packet->size = packet->overhead;" to reset size to the current overhead
after sending packets, hence the comment in sctp_packet_reset()

 include/net/sctp/sctp.h    |  4 +++-
 include/net/sctp/structs.h |  2 ++
 net/sctp/chunk.c           | 10 +++++++---
 net/sctp/ipv6.c            | 45 ++++++++++++++++++++++++++++++++++++++-------
 net/sctp/output.c          | 34 +++++++++++++++++++++-------------
 net/sctp/protocol.c        | 38 ++++++++++++++++++++++++++++++++++++++
 net/sctp/socket.c          | 11 ++++++++---
 7 files changed, 117 insertions(+), 27 deletions(-)

Comments

Neil Horman Feb. 21, 2018, 3:44 a.m. UTC | #1
On Tue, Feb 20, 2018 at 07:15:27PM +0000, Richard Haines wrote:
> Add ip option support to allow LSM security modules to utilise CIPSO/IPv4
> and CALIPSO/IPv6 services.
> 
> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
> ---
> All SCTP lksctp-tools/src/func_tests run correctly in enforcing mode.
> All "./sctp-tests run" obtained from: https://github.com/sctp/sctp-tests
> pass.
> 
> V7 Changes:
> 1) Log when copy ip options fail for IPv4 and IPv6
> 2) Correct sctp_setsockopt_maxseg() function. Note that the lksctp-tools
> func_tests do not test with struct sctp_assoc_value. Just used simple test
> and okay.
> 3) Move calculation of overheads to sctp_packet_config().
> NOTE: Initially in sctp_packet_reset() I set packet->size and
> packet->overhead to zero (as it is a reset). This was okay for all the
> lksctp-tools function tests, however when running "sctp-tests" ndatshched
> tests it causes these to fail with an st_s.log entry of:
> 	sid: 3, expected: 3
> 	sid: 3, expected: 3
> 	unexpected sid packet !!!
> 	sid: 1, expected: 3
> 
> I then found sctp_packet_transmit() relies on setting
> "packet->size = packet->overhead;" to reset size to the current overhead
> after sending packets, hence the comment in sctp_packet_reset()
> 
>  include/net/sctp/sctp.h    |  4 +++-
>  include/net/sctp/structs.h |  2 ++
>  net/sctp/chunk.c           | 10 +++++++---
>  net/sctp/ipv6.c            | 45 ++++++++++++++++++++++++++++++++++++++-------
>  net/sctp/output.c          | 34 +++++++++++++++++++++-------------
>  net/sctp/protocol.c        | 38 ++++++++++++++++++++++++++++++++++++++
>  net/sctp/socket.c          | 11 ++++++++---
>  7 files changed, 117 insertions(+), 27 deletions(-)
> 
> diff --git a/include/net/sctp/sctp.h b/include/net/sctp/sctp.h
> index f7ae6b0..25c5c87 100644
> --- a/include/net/sctp/sctp.h
> +++ b/include/net/sctp/sctp.h
> @@ -441,9 +441,11 @@ static inline int sctp_list_single_entry(struct list_head *head)
>  static inline int sctp_frag_point(const struct sctp_association *asoc, int pmtu)
>  {
>  	struct sctp_sock *sp = sctp_sk(asoc->base.sk);
> +	struct sctp_af *af = sp->pf->af;
>  	int frag = pmtu;
>  
> -	frag -= sp->pf->af->net_header_len;
> +	frag -= af->ip_options_len(asoc->base.sk);
> +	frag -= af->net_header_len;
>  	frag -= sizeof(struct sctphdr) + sctp_datachk_len(&asoc->stream);
>  
>  	if (asoc->user_frag)
> diff --git a/include/net/sctp/structs.h b/include/net/sctp/structs.h
> index 03e92dd..ead5fce 100644
> --- a/include/net/sctp/structs.h
> +++ b/include/net/sctp/structs.h
> @@ -491,6 +491,7 @@ struct sctp_af {
>  	void		(*ecn_capable)(struct sock *sk);
>  	__u16		net_header_len;
>  	int		sockaddr_len;
> +	int		(*ip_options_len)(struct sock *sk);
>  	sa_family_t	sa_family;
>  	struct list_head list;
>  };
> @@ -515,6 +516,7 @@ struct sctp_pf {
>  	int (*addr_to_user)(struct sctp_sock *sk, union sctp_addr *addr);
>  	void (*to_sk_saddr)(union sctp_addr *, struct sock *sk);
>  	void (*to_sk_daddr)(union sctp_addr *, struct sock *sk);
> +	void (*copy_ip_options)(struct sock *sk, struct sock *newsk);
>  	struct sctp_af *af;
>  };
>  
> diff --git a/net/sctp/chunk.c b/net/sctp/chunk.c
> index 991a530..d726d21 100644
> --- a/net/sctp/chunk.c
> +++ b/net/sctp/chunk.c
> @@ -171,6 +171,8 @@ struct sctp_datamsg *sctp_datamsg_from_user(struct sctp_association *asoc,
>  	struct list_head *pos, *temp;
>  	struct sctp_chunk *chunk;
>  	struct sctp_datamsg *msg;
> +	struct sctp_sock *sp;
> +	struct sctp_af *af;
>  	int err;
>  
>  	msg = sctp_datamsg_new(GFP_KERNEL);
> @@ -189,9 +191,11 @@ struct sctp_datamsg *sctp_datamsg_from_user(struct sctp_association *asoc,
>  	/* This is the biggest possible DATA chunk that can fit into
>  	 * the packet
>  	 */
> -	max_data = asoc->pathmtu -
> -		   sctp_sk(asoc->base.sk)->pf->af->net_header_len -
> -		   sizeof(struct sctphdr) - sctp_datachk_len(&asoc->stream);
> +	sp = sctp_sk(asoc->base.sk);
> +	af = sp->pf->af;
> +	max_data = asoc->pathmtu - af->net_header_len -
> +		   sizeof(struct sctphdr) - sctp_datachk_len(&asoc->stream) -
> +		   af->ip_options_len(asoc->base.sk);
>  	max_data = SCTP_TRUNC4(max_data);
>  
>  	/* If the the peer requested that we authenticate DATA chunks
> diff --git a/net/sctp/ipv6.c b/net/sctp/ipv6.c
> index e35d4f7..30a05a8 100644
> --- a/net/sctp/ipv6.c
> +++ b/net/sctp/ipv6.c
> @@ -427,6 +427,41 @@ static void sctp_v6_copy_addrlist(struct list_head *addrlist,
>  	rcu_read_unlock();
>  }
>  
> +/* Copy over any ip options */
> +static void sctp_v6_copy_ip_options(struct sock *sk, struct sock *newsk)
> +{
> +	struct ipv6_pinfo *newnp, *np = inet6_sk(sk);
> +	struct ipv6_txoptions *opt;
> +
> +	newnp = inet6_sk(newsk);
> +
> +	rcu_read_lock();
> +	opt = rcu_dereference(np->opt);
> +	if (opt) {
> +		opt = ipv6_dup_options(newsk, opt);
> +		if (!opt)
> +			pr_err("%s: Failed to copy ip options\n", __func__);
> +	}
> +	RCU_INIT_POINTER(newnp->opt, opt);
> +	rcu_read_unlock();
> +}
> +
> +/* Account for the IP options */
> +static int sctp_v6_ip_options_len(struct sock *sk)
> +{
> +	struct ipv6_pinfo *np = inet6_sk(sk);
> +	struct ipv6_txoptions *opt;
> +	int len = 0;
> +
> +	rcu_read_lock();
> +	opt = rcu_dereference(np->opt);
> +	if (opt)
> +		len = opt->opt_flen + opt->opt_nflen;
> +
> +	rcu_read_unlock();
> +	return len;
> +}
> +
>  /* Initialize a sockaddr_storage from in incoming skb. */
>  static void sctp_v6_from_skb(union sctp_addr *addr, struct sk_buff *skb,
>  			     int is_saddr)
> @@ -666,7 +701,6 @@ static struct sock *sctp_v6_create_accept_sk(struct sock *sk,
>  	struct sock *newsk;
>  	struct ipv6_pinfo *newnp, *np = inet6_sk(sk);
>  	struct sctp6_sock *newsctp6sk;
> -	struct ipv6_txoptions *opt;
>  
>  	newsk = sk_alloc(sock_net(sk), PF_INET6, GFP_KERNEL, sk->sk_prot, kern);
>  	if (!newsk)
> @@ -689,12 +723,7 @@ static struct sock *sctp_v6_create_accept_sk(struct sock *sk,
>  	newnp->ipv6_ac_list = NULL;
>  	newnp->ipv6_fl_list = NULL;
>  
> -	rcu_read_lock();
> -	opt = rcu_dereference(np->opt);
> -	if (opt)
> -		opt = ipv6_dup_options(newsk, opt);
> -	RCU_INIT_POINTER(newnp->opt, opt);
> -	rcu_read_unlock();
> +	sctp_v6_copy_ip_options(sk, newsk);
>  
>  	/* Initialize sk's sport, dport, rcv_saddr and daddr for getsockname()
>  	 * and getpeername().
> @@ -1041,6 +1070,7 @@ static struct sctp_af sctp_af_inet6 = {
>  	.ecn_capable	   = sctp_v6_ecn_capable,
>  	.net_header_len	   = sizeof(struct ipv6hdr),
>  	.sockaddr_len	   = sizeof(struct sockaddr_in6),
> +	.ip_options_len	   = sctp_v6_ip_options_len,
>  #ifdef CONFIG_COMPAT
>  	.compat_setsockopt = compat_ipv6_setsockopt,
>  	.compat_getsockopt = compat_ipv6_getsockopt,
> @@ -1059,6 +1089,7 @@ static struct sctp_pf sctp_pf_inet6 = {
>  	.addr_to_user  = sctp_v6_addr_to_user,
>  	.to_sk_saddr   = sctp_v6_to_sk_saddr,
>  	.to_sk_daddr   = sctp_v6_to_sk_daddr,
> +	.copy_ip_options = sctp_v6_copy_ip_options,
>  	.af            = &sctp_af_inet6,
>  };
>  
> diff --git a/net/sctp/output.c b/net/sctp/output.c
> index 01a26ee..a58d13c 100644
> --- a/net/sctp/output.c
> +++ b/net/sctp/output.c
> @@ -69,7 +69,11 @@ static enum sctp_xmit sctp_packet_will_fit(struct sctp_packet *packet,
>  
>  static void sctp_packet_reset(struct sctp_packet *packet)
>  {
> +	/* sctp_packet_transmit() relies on this to reset size to the
> +	 * current overhead after sending packets.
> +	 */
>  	packet->size = packet->overhead;
> +
>  	packet->has_cookie_echo = 0;
>  	packet->has_sack = 0;
>  	packet->has_data = 0;
> @@ -87,6 +91,7 @@ void sctp_packet_config(struct sctp_packet *packet, __u32 vtag,
>  	struct sctp_transport *tp = packet->transport;
>  	struct sctp_association *asoc = tp->asoc;
>  	struct sock *sk;
> +	size_t overhead = sizeof(struct ipv6hdr) + sizeof(struct sctphdr);
>  
>  	pr_debug("%s: packet:%p vtag:0x%x\n", __func__, packet, vtag);
>  	packet->vtag = vtag;
> @@ -95,10 +100,22 @@ void sctp_packet_config(struct sctp_packet *packet, __u32 vtag,
>  	if (!sctp_packet_empty(packet))
>  		return;
>  
> -	/* set packet max_size with pathmtu */
> +	/* set packet max_size with pathmtu, then calculate overhead */
>  	packet->max_size = tp->pathmtu;
> -	if (!asoc)
> +	if (asoc) {
> +		struct sctp_sock *sp = sctp_sk(asoc->base.sk);
> +		struct sctp_af *af = sp->pf->af;
> +
> +		overhead = af->net_header_len +
> +			   af->ip_options_len(asoc->base.sk);
> +		overhead += sizeof(struct sctphdr);
> +		packet->overhead = overhead;
> +		packet->size = overhead;
> +	} else {
> +		packet->overhead = overhead;
> +		packet->size = overhead;
>  		return;
> +	}
>  
>  	/* update dst or transport pathmtu if in need */
>  	sk = asoc->base.sk;
> @@ -140,23 +157,14 @@ void sctp_packet_init(struct sctp_packet *packet,
>  		      struct sctp_transport *transport,
>  		      __u16 sport, __u16 dport)
>  {
> -	struct sctp_association *asoc = transport->asoc;
> -	size_t overhead;
> -
>  	pr_debug("%s: packet:%p transport:%p\n", __func__, packet, transport);
>  
>  	packet->transport = transport;
>  	packet->source_port = sport;
>  	packet->destination_port = dport;
>  	INIT_LIST_HEAD(&packet->chunk_list);
> -	if (asoc) {
> -		struct sctp_sock *sp = sctp_sk(asoc->base.sk);
> -		overhead = sp->pf->af->net_header_len;
> -	} else {
> -		overhead = sizeof(struct ipv6hdr);
> -	}
> -	overhead += sizeof(struct sctphdr);
> -	packet->overhead = overhead;
> +	/* The overhead will be calculated by sctp_packet_config() */
> +	packet->overhead = 0;
>  	sctp_packet_reset(packet);
>  	packet->vtag = 0;
>  }
> diff --git a/net/sctp/protocol.c b/net/sctp/protocol.c
> index 91813e6..01c4d77 100644
> --- a/net/sctp/protocol.c
> +++ b/net/sctp/protocol.c
> @@ -237,6 +237,40 @@ int sctp_copy_local_addr_list(struct net *net, struct sctp_bind_addr *bp,
>  	return error;
>  }
>  
> +/* Copy over any ip options */
> +static void sctp_v4_copy_ip_options(struct sock *sk, struct sock *newsk)
> +{
> +	struct inet_sock *newinet, *inet = inet_sk(sk);
> +	struct ip_options_rcu *inet_opt, *newopt = NULL;
> +
> +	newinet = inet_sk(newsk);
> +
> +	rcu_read_lock();
> +	inet_opt = rcu_dereference(inet->inet_opt);
> +	if (inet_opt) {
> +		newopt = sock_kmalloc(newsk, sizeof(*inet_opt) +
> +				      inet_opt->opt.optlen, GFP_ATOMIC);
> +		if (newopt)
> +			memcpy(newopt, inet_opt, sizeof(*inet_opt) +
> +			       inet_opt->opt.optlen);
> +		else
> +			pr_err("%s: Failed to copy ip options\n", __func__);
> +	}
> +	RCU_INIT_POINTER(newinet->inet_opt, newopt);
> +	rcu_read_unlock();
> +}
> +
> +/* Account for the IP options */
> +static int sctp_v4_ip_options_len(struct sock *sk)
> +{
> +	struct inet_sock *inet = inet_sk(sk);
> +
> +	if (inet->inet_opt)
> +		return inet->inet_opt->opt.optlen;
> +	else
> +		return 0;
> +}
> +
>  /* Initialize a sctp_addr from in incoming skb.  */
>  static void sctp_v4_from_skb(union sctp_addr *addr, struct sk_buff *skb,
>  			     int is_saddr)
> @@ -588,6 +622,8 @@ static struct sock *sctp_v4_create_accept_sk(struct sock *sk,
>  	sctp_copy_sock(newsk, sk, asoc);
>  	sock_reset_flag(newsk, SOCK_ZAPPED);
>  
> +	sctp_v4_copy_ip_options(sk, newsk);
> +
>  	newinet = inet_sk(newsk);
>  
>  	newinet->inet_daddr = asoc->peer.primary_addr.v4.sin_addr.s_addr;
> @@ -1006,6 +1042,7 @@ static struct sctp_pf sctp_pf_inet = {
>  	.addr_to_user  = sctp_v4_addr_to_user,
>  	.to_sk_saddr   = sctp_v4_to_sk_saddr,
>  	.to_sk_daddr   = sctp_v4_to_sk_daddr,
> +	.copy_ip_options = sctp_v4_copy_ip_options,
>  	.af            = &sctp_af_inet
>  };
>  
> @@ -1090,6 +1127,7 @@ static struct sctp_af sctp_af_inet = {
>  	.ecn_capable	   = sctp_v4_ecn_capable,
>  	.net_header_len	   = sizeof(struct iphdr),
>  	.sockaddr_len	   = sizeof(struct sockaddr_in),
> +	.ip_options_len	   = sctp_v4_ip_options_len,
>  #ifdef CONFIG_COMPAT
>  	.compat_setsockopt = compat_ip_setsockopt,
>  	.compat_getsockopt = compat_ip_getsockopt,
> diff --git a/net/sctp/socket.c b/net/sctp/socket.c
> index bf271f8..eb55c63 100644
> --- a/net/sctp/socket.c
> +++ b/net/sctp/socket.c
> @@ -3138,6 +3138,7 @@ static int sctp_setsockopt_mappedv4(struct sock *sk, char __user *optval, unsign
>  static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned int optlen)
>  {
>  	struct sctp_sock *sp = sctp_sk(sk);
> +	struct sctp_af *af = sp->pf->af;
>  	struct sctp_assoc_value params;
>  	struct sctp_association *asoc;
>  	int val;
> @@ -3162,7 +3163,8 @@ static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned
>  	if (val) {
>  		int min_len, max_len;
>  
> -		min_len = SCTP_DEFAULT_MINSEGMENT - sp->pf->af->net_header_len;
> +		min_len = SCTP_DEFAULT_MINSEGMENT - af->net_header_len;
> +		min_len -= af->ip_options_len(sk);
>  		min_len -= sizeof(struct sctphdr) +
>  			   sizeof(struct sctp_data_chunk);
>  
> @@ -3175,7 +3177,8 @@ static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned
>  	asoc = sctp_id2assoc(sk, params.assoc_id);
>  	if (asoc) {
>  		if (val == 0) {
> -			val = asoc->pathmtu - sp->pf->af->net_header_len;
> +			val = asoc->pathmtu - af->net_header_len;
> +			val -= af->ip_options_len(sk);
>  			val -= sizeof(struct sctphdr) +
>  			       sctp_datachk_len(&asoc->stream);
>  		}
> @@ -5087,9 +5090,11 @@ int sctp_do_peeloff(struct sock *sk, sctp_assoc_t id, struct socket **sockp)
>  	sctp_copy_sock(sock->sk, sk, asoc);
>  
>  	/* Make peeled-off sockets more like 1-1 accepted sockets.
> -	 * Set the daddr and initialize id to something more random
> +	 * Set the daddr and initialize id to something more random and also
> +	 * copy over any ip options.
>  	 */
>  	sp->pf->to_sk_daddr(&asoc->peer.primary_addr, sk);
> +	sp->pf->copy_ip_options(sk, sock->sk);
>  
>  	/* Populate the fields of the newsk from the oldsk and migrate the
>  	 * asoc to the newsk.
> -- 
> 2.14.3
> 
> 
I think this makes a good bit more sense, thanks for the extra pass
Acked-by: Neil Horman <nhorman@tuxdriver.com>

--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Marcelo Ricardo Leitner Feb. 21, 2018, 2:33 p.m. UTC | #2
On Tue, Feb 20, 2018 at 07:15:27PM +0000, Richard Haines wrote:
> Add ip option support to allow LSM security modules to utilise CIPSO/IPv4
> and CALIPSO/IPv6 services.
> 
> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>

LGTM too, thanks!

Acked-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>

> ---
> All SCTP lksctp-tools/src/func_tests run correctly in enforcing mode.
> All "./sctp-tests run" obtained from: https://github.com/sctp/sctp-tests
> pass.
> 
> V7 Changes:
> 1) Log when copy ip options fail for IPv4 and IPv6
> 2) Correct sctp_setsockopt_maxseg() function. Note that the lksctp-tools
> func_tests do not test with struct sctp_assoc_value. Just used simple test
> and okay.
> 3) Move calculation of overheads to sctp_packet_config().
> NOTE: Initially in sctp_packet_reset() I set packet->size and
> packet->overhead to zero (as it is a reset). This was okay for all the
> lksctp-tools function tests, however when running "sctp-tests" ndatshched
> tests it causes these to fail with an st_s.log entry of:
> 	sid: 3, expected: 3
> 	sid: 3, expected: 3
> 	unexpected sid packet !!!
> 	sid: 1, expected: 3
> 
> I then found sctp_packet_transmit() relies on setting
> "packet->size = packet->overhead;" to reset size to the current overhead
> after sending packets, hence the comment in sctp_packet_reset()
> 
>  include/net/sctp/sctp.h    |  4 +++-
>  include/net/sctp/structs.h |  2 ++
>  net/sctp/chunk.c           | 10 +++++++---
>  net/sctp/ipv6.c            | 45 ++++++++++++++++++++++++++++++++++++++-------
>  net/sctp/output.c          | 34 +++++++++++++++++++++-------------
>  net/sctp/protocol.c        | 38 ++++++++++++++++++++++++++++++++++++++
>  net/sctp/socket.c          | 11 ++++++++---
>  7 files changed, 117 insertions(+), 27 deletions(-)
> 
> diff --git a/include/net/sctp/sctp.h b/include/net/sctp/sctp.h
> index f7ae6b0..25c5c87 100644
> --- a/include/net/sctp/sctp.h
> +++ b/include/net/sctp/sctp.h
> @@ -441,9 +441,11 @@ static inline int sctp_list_single_entry(struct list_head *head)
>  static inline int sctp_frag_point(const struct sctp_association *asoc, int pmtu)
>  {
>  	struct sctp_sock *sp = sctp_sk(asoc->base.sk);
> +	struct sctp_af *af = sp->pf->af;
>  	int frag = pmtu;
>  
> -	frag -= sp->pf->af->net_header_len;
> +	frag -= af->ip_options_len(asoc->base.sk);
> +	frag -= af->net_header_len;
>  	frag -= sizeof(struct sctphdr) + sctp_datachk_len(&asoc->stream);
>  
>  	if (asoc->user_frag)
> diff --git a/include/net/sctp/structs.h b/include/net/sctp/structs.h
> index 03e92dd..ead5fce 100644
> --- a/include/net/sctp/structs.h
> +++ b/include/net/sctp/structs.h
> @@ -491,6 +491,7 @@ struct sctp_af {
>  	void		(*ecn_capable)(struct sock *sk);
>  	__u16		net_header_len;
>  	int		sockaddr_len;
> +	int		(*ip_options_len)(struct sock *sk);
>  	sa_family_t	sa_family;
>  	struct list_head list;
>  };
> @@ -515,6 +516,7 @@ struct sctp_pf {
>  	int (*addr_to_user)(struct sctp_sock *sk, union sctp_addr *addr);
>  	void (*to_sk_saddr)(union sctp_addr *, struct sock *sk);
>  	void (*to_sk_daddr)(union sctp_addr *, struct sock *sk);
> +	void (*copy_ip_options)(struct sock *sk, struct sock *newsk);
>  	struct sctp_af *af;
>  };
>  
> diff --git a/net/sctp/chunk.c b/net/sctp/chunk.c
> index 991a530..d726d21 100644
> --- a/net/sctp/chunk.c
> +++ b/net/sctp/chunk.c
> @@ -171,6 +171,8 @@ struct sctp_datamsg *sctp_datamsg_from_user(struct sctp_association *asoc,
>  	struct list_head *pos, *temp;
>  	struct sctp_chunk *chunk;
>  	struct sctp_datamsg *msg;
> +	struct sctp_sock *sp;
> +	struct sctp_af *af;
>  	int err;
>  
>  	msg = sctp_datamsg_new(GFP_KERNEL);
> @@ -189,9 +191,11 @@ struct sctp_datamsg *sctp_datamsg_from_user(struct sctp_association *asoc,
>  	/* This is the biggest possible DATA chunk that can fit into
>  	 * the packet
>  	 */
> -	max_data = asoc->pathmtu -
> -		   sctp_sk(asoc->base.sk)->pf->af->net_header_len -
> -		   sizeof(struct sctphdr) - sctp_datachk_len(&asoc->stream);
> +	sp = sctp_sk(asoc->base.sk);
> +	af = sp->pf->af;
> +	max_data = asoc->pathmtu - af->net_header_len -
> +		   sizeof(struct sctphdr) - sctp_datachk_len(&asoc->stream) -
> +		   af->ip_options_len(asoc->base.sk);
>  	max_data = SCTP_TRUNC4(max_data);
>  
>  	/* If the the peer requested that we authenticate DATA chunks
> diff --git a/net/sctp/ipv6.c b/net/sctp/ipv6.c
> index e35d4f7..30a05a8 100644
> --- a/net/sctp/ipv6.c
> +++ b/net/sctp/ipv6.c
> @@ -427,6 +427,41 @@ static void sctp_v6_copy_addrlist(struct list_head *addrlist,
>  	rcu_read_unlock();
>  }
>  
> +/* Copy over any ip options */
> +static void sctp_v6_copy_ip_options(struct sock *sk, struct sock *newsk)
> +{
> +	struct ipv6_pinfo *newnp, *np = inet6_sk(sk);
> +	struct ipv6_txoptions *opt;
> +
> +	newnp = inet6_sk(newsk);
> +
> +	rcu_read_lock();
> +	opt = rcu_dereference(np->opt);
> +	if (opt) {
> +		opt = ipv6_dup_options(newsk, opt);
> +		if (!opt)
> +			pr_err("%s: Failed to copy ip options\n", __func__);
> +	}
> +	RCU_INIT_POINTER(newnp->opt, opt);
> +	rcu_read_unlock();
> +}
> +
> +/* Account for the IP options */
> +static int sctp_v6_ip_options_len(struct sock *sk)
> +{
> +	struct ipv6_pinfo *np = inet6_sk(sk);
> +	struct ipv6_txoptions *opt;
> +	int len = 0;
> +
> +	rcu_read_lock();
> +	opt = rcu_dereference(np->opt);
> +	if (opt)
> +		len = opt->opt_flen + opt->opt_nflen;
> +
> +	rcu_read_unlock();
> +	return len;
> +}
> +
>  /* Initialize a sockaddr_storage from in incoming skb. */
>  static void sctp_v6_from_skb(union sctp_addr *addr, struct sk_buff *skb,
>  			     int is_saddr)
> @@ -666,7 +701,6 @@ static struct sock *sctp_v6_create_accept_sk(struct sock *sk,
>  	struct sock *newsk;
>  	struct ipv6_pinfo *newnp, *np = inet6_sk(sk);
>  	struct sctp6_sock *newsctp6sk;
> -	struct ipv6_txoptions *opt;
>  
>  	newsk = sk_alloc(sock_net(sk), PF_INET6, GFP_KERNEL, sk->sk_prot, kern);
>  	if (!newsk)
> @@ -689,12 +723,7 @@ static struct sock *sctp_v6_create_accept_sk(struct sock *sk,
>  	newnp->ipv6_ac_list = NULL;
>  	newnp->ipv6_fl_list = NULL;
>  
> -	rcu_read_lock();
> -	opt = rcu_dereference(np->opt);
> -	if (opt)
> -		opt = ipv6_dup_options(newsk, opt);
> -	RCU_INIT_POINTER(newnp->opt, opt);
> -	rcu_read_unlock();
> +	sctp_v6_copy_ip_options(sk, newsk);
>  
>  	/* Initialize sk's sport, dport, rcv_saddr and daddr for getsockname()
>  	 * and getpeername().
> @@ -1041,6 +1070,7 @@ static struct sctp_af sctp_af_inet6 = {
>  	.ecn_capable	   = sctp_v6_ecn_capable,
>  	.net_header_len	   = sizeof(struct ipv6hdr),
>  	.sockaddr_len	   = sizeof(struct sockaddr_in6),
> +	.ip_options_len	   = sctp_v6_ip_options_len,
>  #ifdef CONFIG_COMPAT
>  	.compat_setsockopt = compat_ipv6_setsockopt,
>  	.compat_getsockopt = compat_ipv6_getsockopt,
> @@ -1059,6 +1089,7 @@ static struct sctp_pf sctp_pf_inet6 = {
>  	.addr_to_user  = sctp_v6_addr_to_user,
>  	.to_sk_saddr   = sctp_v6_to_sk_saddr,
>  	.to_sk_daddr   = sctp_v6_to_sk_daddr,
> +	.copy_ip_options = sctp_v6_copy_ip_options,
>  	.af            = &sctp_af_inet6,
>  };
>  
> diff --git a/net/sctp/output.c b/net/sctp/output.c
> index 01a26ee..a58d13c 100644
> --- a/net/sctp/output.c
> +++ b/net/sctp/output.c
> @@ -69,7 +69,11 @@ static enum sctp_xmit sctp_packet_will_fit(struct sctp_packet *packet,
>  
>  static void sctp_packet_reset(struct sctp_packet *packet)
>  {
> +	/* sctp_packet_transmit() relies on this to reset size to the
> +	 * current overhead after sending packets.
> +	 */
>  	packet->size = packet->overhead;
> +
>  	packet->has_cookie_echo = 0;
>  	packet->has_sack = 0;
>  	packet->has_data = 0;
> @@ -87,6 +91,7 @@ void sctp_packet_config(struct sctp_packet *packet, __u32 vtag,
>  	struct sctp_transport *tp = packet->transport;
>  	struct sctp_association *asoc = tp->asoc;
>  	struct sock *sk;
> +	size_t overhead = sizeof(struct ipv6hdr) + sizeof(struct sctphdr);
>  
>  	pr_debug("%s: packet:%p vtag:0x%x\n", __func__, packet, vtag);
>  	packet->vtag = vtag;
> @@ -95,10 +100,22 @@ void sctp_packet_config(struct sctp_packet *packet, __u32 vtag,
>  	if (!sctp_packet_empty(packet))
>  		return;
>  
> -	/* set packet max_size with pathmtu */
> +	/* set packet max_size with pathmtu, then calculate overhead */
>  	packet->max_size = tp->pathmtu;
> -	if (!asoc)
> +	if (asoc) {
> +		struct sctp_sock *sp = sctp_sk(asoc->base.sk);
> +		struct sctp_af *af = sp->pf->af;
> +
> +		overhead = af->net_header_len +
> +			   af->ip_options_len(asoc->base.sk);
> +		overhead += sizeof(struct sctphdr);
> +		packet->overhead = overhead;
> +		packet->size = overhead;
> +	} else {
> +		packet->overhead = overhead;
> +		packet->size = overhead;
>  		return;
> +	}
>  
>  	/* update dst or transport pathmtu if in need */
>  	sk = asoc->base.sk;
> @@ -140,23 +157,14 @@ void sctp_packet_init(struct sctp_packet *packet,
>  		      struct sctp_transport *transport,
>  		      __u16 sport, __u16 dport)
>  {
> -	struct sctp_association *asoc = transport->asoc;
> -	size_t overhead;
> -
>  	pr_debug("%s: packet:%p transport:%p\n", __func__, packet, transport);
>  
>  	packet->transport = transport;
>  	packet->source_port = sport;
>  	packet->destination_port = dport;
>  	INIT_LIST_HEAD(&packet->chunk_list);
> -	if (asoc) {
> -		struct sctp_sock *sp = sctp_sk(asoc->base.sk);
> -		overhead = sp->pf->af->net_header_len;
> -	} else {
> -		overhead = sizeof(struct ipv6hdr);
> -	}
> -	overhead += sizeof(struct sctphdr);
> -	packet->overhead = overhead;
> +	/* The overhead will be calculated by sctp_packet_config() */
> +	packet->overhead = 0;
>  	sctp_packet_reset(packet);
>  	packet->vtag = 0;
>  }
> diff --git a/net/sctp/protocol.c b/net/sctp/protocol.c
> index 91813e6..01c4d77 100644
> --- a/net/sctp/protocol.c
> +++ b/net/sctp/protocol.c
> @@ -237,6 +237,40 @@ int sctp_copy_local_addr_list(struct net *net, struct sctp_bind_addr *bp,
>  	return error;
>  }
>  
> +/* Copy over any ip options */
> +static void sctp_v4_copy_ip_options(struct sock *sk, struct sock *newsk)
> +{
> +	struct inet_sock *newinet, *inet = inet_sk(sk);
> +	struct ip_options_rcu *inet_opt, *newopt = NULL;
> +
> +	newinet = inet_sk(newsk);
> +
> +	rcu_read_lock();
> +	inet_opt = rcu_dereference(inet->inet_opt);
> +	if (inet_opt) {
> +		newopt = sock_kmalloc(newsk, sizeof(*inet_opt) +
> +				      inet_opt->opt.optlen, GFP_ATOMIC);
> +		if (newopt)
> +			memcpy(newopt, inet_opt, sizeof(*inet_opt) +
> +			       inet_opt->opt.optlen);
> +		else
> +			pr_err("%s: Failed to copy ip options\n", __func__);
> +	}
> +	RCU_INIT_POINTER(newinet->inet_opt, newopt);
> +	rcu_read_unlock();
> +}
> +
> +/* Account for the IP options */
> +static int sctp_v4_ip_options_len(struct sock *sk)
> +{
> +	struct inet_sock *inet = inet_sk(sk);
> +
> +	if (inet->inet_opt)
> +		return inet->inet_opt->opt.optlen;
> +	else
> +		return 0;
> +}
> +
>  /* Initialize a sctp_addr from in incoming skb.  */
>  static void sctp_v4_from_skb(union sctp_addr *addr, struct sk_buff *skb,
>  			     int is_saddr)
> @@ -588,6 +622,8 @@ static struct sock *sctp_v4_create_accept_sk(struct sock *sk,
>  	sctp_copy_sock(newsk, sk, asoc);
>  	sock_reset_flag(newsk, SOCK_ZAPPED);
>  
> +	sctp_v4_copy_ip_options(sk, newsk);
> +
>  	newinet = inet_sk(newsk);
>  
>  	newinet->inet_daddr = asoc->peer.primary_addr.v4.sin_addr.s_addr;
> @@ -1006,6 +1042,7 @@ static struct sctp_pf sctp_pf_inet = {
>  	.addr_to_user  = sctp_v4_addr_to_user,
>  	.to_sk_saddr   = sctp_v4_to_sk_saddr,
>  	.to_sk_daddr   = sctp_v4_to_sk_daddr,
> +	.copy_ip_options = sctp_v4_copy_ip_options,
>  	.af            = &sctp_af_inet
>  };
>  
> @@ -1090,6 +1127,7 @@ static struct sctp_af sctp_af_inet = {
>  	.ecn_capable	   = sctp_v4_ecn_capable,
>  	.net_header_len	   = sizeof(struct iphdr),
>  	.sockaddr_len	   = sizeof(struct sockaddr_in),
> +	.ip_options_len	   = sctp_v4_ip_options_len,
>  #ifdef CONFIG_COMPAT
>  	.compat_setsockopt = compat_ip_setsockopt,
>  	.compat_getsockopt = compat_ip_getsockopt,
> diff --git a/net/sctp/socket.c b/net/sctp/socket.c
> index bf271f8..eb55c63 100644
> --- a/net/sctp/socket.c
> +++ b/net/sctp/socket.c
> @@ -3138,6 +3138,7 @@ static int sctp_setsockopt_mappedv4(struct sock *sk, char __user *optval, unsign
>  static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned int optlen)
>  {
>  	struct sctp_sock *sp = sctp_sk(sk);
> +	struct sctp_af *af = sp->pf->af;
>  	struct sctp_assoc_value params;
>  	struct sctp_association *asoc;
>  	int val;
> @@ -3162,7 +3163,8 @@ static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned
>  	if (val) {
>  		int min_len, max_len;
>  
> -		min_len = SCTP_DEFAULT_MINSEGMENT - sp->pf->af->net_header_len;
> +		min_len = SCTP_DEFAULT_MINSEGMENT - af->net_header_len;
> +		min_len -= af->ip_options_len(sk);
>  		min_len -= sizeof(struct sctphdr) +
>  			   sizeof(struct sctp_data_chunk);
>  
> @@ -3175,7 +3177,8 @@ static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned
>  	asoc = sctp_id2assoc(sk, params.assoc_id);
>  	if (asoc) {
>  		if (val == 0) {
> -			val = asoc->pathmtu - sp->pf->af->net_header_len;
> +			val = asoc->pathmtu - af->net_header_len;
> +			val -= af->ip_options_len(sk);
>  			val -= sizeof(struct sctphdr) +
>  			       sctp_datachk_len(&asoc->stream);
>  		}
> @@ -5087,9 +5090,11 @@ int sctp_do_peeloff(struct sock *sk, sctp_assoc_t id, struct socket **sockp)
>  	sctp_copy_sock(sock->sk, sk, asoc);
>  
>  	/* Make peeled-off sockets more like 1-1 accepted sockets.
> -	 * Set the daddr and initialize id to something more random
> +	 * Set the daddr and initialize id to something more random and also
> +	 * copy over any ip options.
>  	 */
>  	sp->pf->to_sk_daddr(&asoc->peer.primary_addr, sk);
> +	sp->pf->copy_ip_options(sk, sock->sk);
>  
>  	/* Populate the fields of the newsk from the oldsk and migrate the
>  	 * asoc to the newsk.
> -- 
> 2.14.3
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-sctp" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Paul Moore Feb. 21, 2018, 8:45 p.m. UTC | #3
On February 21, 2018 9:33:51 AM Marcelo Ricardo Leitner <marcelo.leitner@gmail.com> wrote:
> On Tue, Feb 20, 2018 at 07:15:27PM +0000, Richard Haines wrote:
>> Add ip option support to allow LSM security modules to utilise CIPSO/IPv4
>> and CALIPSO/IPv6 services.
>> 
>> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
>
> LGTM too, thanks!
>
> Acked-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>

I agree, thanks everyone for all the work, review, and patience behind this patchset!  I'll work on merging this into selinux/next and I'll send a note when it's done.

--
paul moore
www.paul-moore.com


--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Paul Moore Feb. 22, 2018, 11:08 p.m. UTC | #4
On Wed, Feb 21, 2018 at 3:45 PM, Paul Moore <paul@paul-moore.com> wrote:
> On February 21, 2018 9:33:51 AM Marcelo Ricardo Leitner <marcelo.leitner@gmail.com> wrote:
>> On Tue, Feb 20, 2018 at 07:15:27PM +0000, Richard Haines wrote:
>>> Add ip option support to allow LSM security modules to utilise CIPSO/IPv4
>>> and CALIPSO/IPv6 services.
>>>
>>> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
>>
>> LGTM too, thanks!
>>
>> Acked-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
>
> I agree, thanks everyone for all the work, review, and patience behind this patchset!  I'll work on merging this into selinux/next and I'll send a note when it's done.

I just merged the four patches (1,3,4 from the v6 patchset, 2 from the
v7 patchset) in selinux/next and did a quick sanity test on the kernel
(booted, no basic SELinux regressions).  Additional testing help is
always appreciated ...

* git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git
* https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git
Marcelo Ricardo Leitner Feb. 23, 2018, 2:40 a.m. UTC | #5
On Thu, Feb 22, 2018 at 06:08:05PM -0500, Paul Moore wrote:
> On Wed, Feb 21, 2018 at 3:45 PM, Paul Moore <paul@paul-moore.com> wrote:
> > On February 21, 2018 9:33:51 AM Marcelo Ricardo Leitner <marcelo.leitner@gmail.com> wrote:
> >> On Tue, Feb 20, 2018 at 07:15:27PM +0000, Richard Haines wrote:
> >>> Add ip option support to allow LSM security modules to utilise CIPSO/IPv4
> >>> and CALIPSO/IPv6 services.
> >>>
> >>> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
> >>
> >> LGTM too, thanks!
> >>
> >> Acked-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
> >
> > I agree, thanks everyone for all the work, review, and patience behind this patchset!  I'll work on merging this into selinux/next and I'll send a note when it's done.
> 
> I just merged the four patches (1,3,4 from the v6 patchset, 2 from the
> v7 patchset) in selinux/next and did a quick sanity test on the kernel
> (booted, no basic SELinux regressions).  Additional testing help is
> always appreciated ...

I'll try it early next week.

Any ideas on when this is going to appear on Dave's net-next tree?
We have a lot of SCTP changes to be posted on this cycle and would be
nice if we could avoid merge conflicts.

> 
> * git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git
> * https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git
> 
> -- 
> paul moore
> www.paul-moore.com
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Paul Moore Feb. 23, 2018, 4:11 p.m. UTC | #6
On Thu, Feb 22, 2018 at 9:40 PM, Marcelo Ricardo Leitner
<marcelo.leitner@gmail.com> wrote:
> On Thu, Feb 22, 2018 at 06:08:05PM -0500, Paul Moore wrote:
>> On Wed, Feb 21, 2018 at 3:45 PM, Paul Moore <paul@paul-moore.com> wrote:
>> > On February 21, 2018 9:33:51 AM Marcelo Ricardo Leitner <marcelo.leitner@gmail.com> wrote:
>> >> On Tue, Feb 20, 2018 at 07:15:27PM +0000, Richard Haines wrote:
>> >>> Add ip option support to allow LSM security modules to utilise CIPSO/IPv4
>> >>> and CALIPSO/IPv6 services.
>> >>>
>> >>> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
>> >>
>> >> LGTM too, thanks!
>> >>
>> >> Acked-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
>> >
>> > I agree, thanks everyone for all the work, review, and patience behind this patchset!  I'll work on merging this into selinux/next and I'll send a note when it's done.
>>
>> I just merged the four patches (1,3,4 from the v6 patchset, 2 from the
>> v7 patchset) in selinux/next and did a quick sanity test on the kernel
>> (booted, no basic SELinux regressions).  Additional testing help is
>> always appreciated ...
>
> I'll try it early next week.
>
> Any ideas on when this is going to appear on Dave's net-next tree?
> We have a lot of SCTP changes to be posted on this cycle and would be
> nice if we could avoid merge conflicts.

It's merged into the SELinux tree, next branch; see the links below.
Last I checked DaveM doesn't pull the selinux/next into his net-next
tree (that would be a little funny for historical reasons).

Any idea on how bad the merge conflicts are?

>> * git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git
>> * https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git
Marcelo Ricardo Leitner Feb. 24, 2018, 12:56 a.m. UTC | #7
On Fri, Feb 23, 2018 at 11:11:50AM -0500, Paul Moore wrote:
> On Thu, Feb 22, 2018 at 9:40 PM, Marcelo Ricardo Leitner
> <marcelo.leitner@gmail.com> wrote:
> > On Thu, Feb 22, 2018 at 06:08:05PM -0500, Paul Moore wrote:
> >> On Wed, Feb 21, 2018 at 3:45 PM, Paul Moore <paul@paul-moore.com> wrote:
> >> > On February 21, 2018 9:33:51 AM Marcelo Ricardo Leitner <marcelo.leitner@gmail.com> wrote:
> >> >> On Tue, Feb 20, 2018 at 07:15:27PM +0000, Richard Haines wrote:
> >> >>> Add ip option support to allow LSM security modules to utilise CIPSO/IPv4
> >> >>> and CALIPSO/IPv6 services.
> >> >>>
> >> >>> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
> >> >>
> >> >> LGTM too, thanks!
> >> >>
> >> >> Acked-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
> >> >
> >> > I agree, thanks everyone for all the work, review, and patience behind this patchset!  I'll work on merging this into selinux/next and I'll send a note when it's done.
> >>
> >> I just merged the four patches (1,3,4 from the v6 patchset, 2 from the
> >> v7 patchset) in selinux/next and did a quick sanity test on the kernel
> >> (booted, no basic SELinux regressions).  Additional testing help is
> >> always appreciated ...
> >
> > I'll try it early next week.
> >
> > Any ideas on when this is going to appear on Dave's net-next tree?
> > We have a lot of SCTP changes to be posted on this cycle and would be
> > nice if we could avoid merge conflicts.
> 
> It's merged into the SELinux tree, next branch; see the links below.
> Last I checked DaveM doesn't pull the selinux/next into his net-next
> tree (that would be a little funny for historical reasons).
> 
> Any idea on how bad the merge conflicts are?

I know about 5 patchsets that we are cooking. For 4 of them I think it
would be mostly fine, perhaps one conflict here and there. But the
other one is a refactoring on MTU handling and it touches lots of
places that 92c49e12646e4 ("sctp: Add ip option support") also
touched, like in the chunk below:

+++ b/include/net/sctp/sctp.h
@@ -441,9 +441,11 @@ static inline int sctp_list_single_entry(struct list_head *head)
 static inline int sctp_frag_point(const struct sctp_association *asoc, int pmtu)
 {
 	struct sctp_sock *sp = sctp_sk(asoc->base.sk);
+	struct sctp_af *af = sp->pf->af;
 	int frag = pmtu;
 
-	frag -= sp->pf->af->net_header_len;
+	frag -= af->ip_options_len(asoc->base.sk);
+	frag -= af->net_header_len;

In the refactor I'm removing this function from here and adding a
similar, not quite the same but similar, in a .c file. 

I post the mtu patchset as RFC next week so we can know better.

  Marcelo

> 
> >> * git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git
> >> * https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git
> 
> -- 
> paul moore
> www.paul-moore.com
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Patch
diff mbox

diff --git a/include/net/sctp/sctp.h b/include/net/sctp/sctp.h
index f7ae6b0..25c5c87 100644
--- a/include/net/sctp/sctp.h
+++ b/include/net/sctp/sctp.h
@@ -441,9 +441,11 @@  static inline int sctp_list_single_entry(struct list_head *head)
 static inline int sctp_frag_point(const struct sctp_association *asoc, int pmtu)
 {
 	struct sctp_sock *sp = sctp_sk(asoc->base.sk);
+	struct sctp_af *af = sp->pf->af;
 	int frag = pmtu;
 
-	frag -= sp->pf->af->net_header_len;
+	frag -= af->ip_options_len(asoc->base.sk);
+	frag -= af->net_header_len;
 	frag -= sizeof(struct sctphdr) + sctp_datachk_len(&asoc->stream);
 
 	if (asoc->user_frag)
diff --git a/include/net/sctp/structs.h b/include/net/sctp/structs.h
index 03e92dd..ead5fce 100644
--- a/include/net/sctp/structs.h
+++ b/include/net/sctp/structs.h
@@ -491,6 +491,7 @@  struct sctp_af {
 	void		(*ecn_capable)(struct sock *sk);
 	__u16		net_header_len;
 	int		sockaddr_len;
+	int		(*ip_options_len)(struct sock *sk);
 	sa_family_t	sa_family;
 	struct list_head list;
 };
@@ -515,6 +516,7 @@  struct sctp_pf {
 	int (*addr_to_user)(struct sctp_sock *sk, union sctp_addr *addr);
 	void (*to_sk_saddr)(union sctp_addr *, struct sock *sk);
 	void (*to_sk_daddr)(union sctp_addr *, struct sock *sk);
+	void (*copy_ip_options)(struct sock *sk, struct sock *newsk);
 	struct sctp_af *af;
 };
 
diff --git a/net/sctp/chunk.c b/net/sctp/chunk.c
index 991a530..d726d21 100644
--- a/net/sctp/chunk.c
+++ b/net/sctp/chunk.c
@@ -171,6 +171,8 @@  struct sctp_datamsg *sctp_datamsg_from_user(struct sctp_association *asoc,
 	struct list_head *pos, *temp;
 	struct sctp_chunk *chunk;
 	struct sctp_datamsg *msg;
+	struct sctp_sock *sp;
+	struct sctp_af *af;
 	int err;
 
 	msg = sctp_datamsg_new(GFP_KERNEL);
@@ -189,9 +191,11 @@  struct sctp_datamsg *sctp_datamsg_from_user(struct sctp_association *asoc,
 	/* This is the biggest possible DATA chunk that can fit into
 	 * the packet
 	 */
-	max_data = asoc->pathmtu -
-		   sctp_sk(asoc->base.sk)->pf->af->net_header_len -
-		   sizeof(struct sctphdr) - sctp_datachk_len(&asoc->stream);
+	sp = sctp_sk(asoc->base.sk);
+	af = sp->pf->af;
+	max_data = asoc->pathmtu - af->net_header_len -
+		   sizeof(struct sctphdr) - sctp_datachk_len(&asoc->stream) -
+		   af->ip_options_len(asoc->base.sk);
 	max_data = SCTP_TRUNC4(max_data);
 
 	/* If the the peer requested that we authenticate DATA chunks
diff --git a/net/sctp/ipv6.c b/net/sctp/ipv6.c
index e35d4f7..30a05a8 100644
--- a/net/sctp/ipv6.c
+++ b/net/sctp/ipv6.c
@@ -427,6 +427,41 @@  static void sctp_v6_copy_addrlist(struct list_head *addrlist,
 	rcu_read_unlock();
 }
 
+/* Copy over any ip options */
+static void sctp_v6_copy_ip_options(struct sock *sk, struct sock *newsk)
+{
+	struct ipv6_pinfo *newnp, *np = inet6_sk(sk);
+	struct ipv6_txoptions *opt;
+
+	newnp = inet6_sk(newsk);
+
+	rcu_read_lock();
+	opt = rcu_dereference(np->opt);
+	if (opt) {
+		opt = ipv6_dup_options(newsk, opt);
+		if (!opt)
+			pr_err("%s: Failed to copy ip options\n", __func__);
+	}
+	RCU_INIT_POINTER(newnp->opt, opt);
+	rcu_read_unlock();
+}
+
+/* Account for the IP options */
+static int sctp_v6_ip_options_len(struct sock *sk)
+{
+	struct ipv6_pinfo *np = inet6_sk(sk);
+	struct ipv6_txoptions *opt;
+	int len = 0;
+
+	rcu_read_lock();
+	opt = rcu_dereference(np->opt);
+	if (opt)
+		len = opt->opt_flen + opt->opt_nflen;
+
+	rcu_read_unlock();
+	return len;
+}
+
 /* Initialize a sockaddr_storage from in incoming skb. */
 static void sctp_v6_from_skb(union sctp_addr *addr, struct sk_buff *skb,
 			     int is_saddr)
@@ -666,7 +701,6 @@  static struct sock *sctp_v6_create_accept_sk(struct sock *sk,
 	struct sock *newsk;
 	struct ipv6_pinfo *newnp, *np = inet6_sk(sk);
 	struct sctp6_sock *newsctp6sk;
-	struct ipv6_txoptions *opt;
 
 	newsk = sk_alloc(sock_net(sk), PF_INET6, GFP_KERNEL, sk->sk_prot, kern);
 	if (!newsk)
@@ -689,12 +723,7 @@  static struct sock *sctp_v6_create_accept_sk(struct sock *sk,
 	newnp->ipv6_ac_list = NULL;
 	newnp->ipv6_fl_list = NULL;
 
-	rcu_read_lock();
-	opt = rcu_dereference(np->opt);
-	if (opt)
-		opt = ipv6_dup_options(newsk, opt);
-	RCU_INIT_POINTER(newnp->opt, opt);
-	rcu_read_unlock();
+	sctp_v6_copy_ip_options(sk, newsk);
 
 	/* Initialize sk's sport, dport, rcv_saddr and daddr for getsockname()
 	 * and getpeername().
@@ -1041,6 +1070,7 @@  static struct sctp_af sctp_af_inet6 = {
 	.ecn_capable	   = sctp_v6_ecn_capable,
 	.net_header_len	   = sizeof(struct ipv6hdr),
 	.sockaddr_len	   = sizeof(struct sockaddr_in6),
+	.ip_options_len	   = sctp_v6_ip_options_len,
 #ifdef CONFIG_COMPAT
 	.compat_setsockopt = compat_ipv6_setsockopt,
 	.compat_getsockopt = compat_ipv6_getsockopt,
@@ -1059,6 +1089,7 @@  static struct sctp_pf sctp_pf_inet6 = {
 	.addr_to_user  = sctp_v6_addr_to_user,
 	.to_sk_saddr   = sctp_v6_to_sk_saddr,
 	.to_sk_daddr   = sctp_v6_to_sk_daddr,
+	.copy_ip_options = sctp_v6_copy_ip_options,
 	.af            = &sctp_af_inet6,
 };
 
diff --git a/net/sctp/output.c b/net/sctp/output.c
index 01a26ee..a58d13c 100644
--- a/net/sctp/output.c
+++ b/net/sctp/output.c
@@ -69,7 +69,11 @@  static enum sctp_xmit sctp_packet_will_fit(struct sctp_packet *packet,
 
 static void sctp_packet_reset(struct sctp_packet *packet)
 {
+	/* sctp_packet_transmit() relies on this to reset size to the
+	 * current overhead after sending packets.
+	 */
 	packet->size = packet->overhead;
+
 	packet->has_cookie_echo = 0;
 	packet->has_sack = 0;
 	packet->has_data = 0;
@@ -87,6 +91,7 @@  void sctp_packet_config(struct sctp_packet *packet, __u32 vtag,
 	struct sctp_transport *tp = packet->transport;
 	struct sctp_association *asoc = tp->asoc;
 	struct sock *sk;
+	size_t overhead = sizeof(struct ipv6hdr) + sizeof(struct sctphdr);
 
 	pr_debug("%s: packet:%p vtag:0x%x\n", __func__, packet, vtag);
 	packet->vtag = vtag;
@@ -95,10 +100,22 @@  void sctp_packet_config(struct sctp_packet *packet, __u32 vtag,
 	if (!sctp_packet_empty(packet))
 		return;
 
-	/* set packet max_size with pathmtu */
+	/* set packet max_size with pathmtu, then calculate overhead */
 	packet->max_size = tp->pathmtu;
-	if (!asoc)
+	if (asoc) {
+		struct sctp_sock *sp = sctp_sk(asoc->base.sk);
+		struct sctp_af *af = sp->pf->af;
+
+		overhead = af->net_header_len +
+			   af->ip_options_len(asoc->base.sk);
+		overhead += sizeof(struct sctphdr);
+		packet->overhead = overhead;
+		packet->size = overhead;
+	} else {
+		packet->overhead = overhead;
+		packet->size = overhead;
 		return;
+	}
 
 	/* update dst or transport pathmtu if in need */
 	sk = asoc->base.sk;
@@ -140,23 +157,14 @@  void sctp_packet_init(struct sctp_packet *packet,
 		      struct sctp_transport *transport,
 		      __u16 sport, __u16 dport)
 {
-	struct sctp_association *asoc = transport->asoc;
-	size_t overhead;
-
 	pr_debug("%s: packet:%p transport:%p\n", __func__, packet, transport);
 
 	packet->transport = transport;
 	packet->source_port = sport;
 	packet->destination_port = dport;
 	INIT_LIST_HEAD(&packet->chunk_list);
-	if (asoc) {
-		struct sctp_sock *sp = sctp_sk(asoc->base.sk);
-		overhead = sp->pf->af->net_header_len;
-	} else {
-		overhead = sizeof(struct ipv6hdr);
-	}
-	overhead += sizeof(struct sctphdr);
-	packet->overhead = overhead;
+	/* The overhead will be calculated by sctp_packet_config() */
+	packet->overhead = 0;
 	sctp_packet_reset(packet);
 	packet->vtag = 0;
 }
diff --git a/net/sctp/protocol.c b/net/sctp/protocol.c
index 91813e6..01c4d77 100644
--- a/net/sctp/protocol.c
+++ b/net/sctp/protocol.c
@@ -237,6 +237,40 @@  int sctp_copy_local_addr_list(struct net *net, struct sctp_bind_addr *bp,
 	return error;
 }
 
+/* Copy over any ip options */
+static void sctp_v4_copy_ip_options(struct sock *sk, struct sock *newsk)
+{
+	struct inet_sock *newinet, *inet = inet_sk(sk);
+	struct ip_options_rcu *inet_opt, *newopt = NULL;
+
+	newinet = inet_sk(newsk);
+
+	rcu_read_lock();
+	inet_opt = rcu_dereference(inet->inet_opt);
+	if (inet_opt) {
+		newopt = sock_kmalloc(newsk, sizeof(*inet_opt) +
+				      inet_opt->opt.optlen, GFP_ATOMIC);
+		if (newopt)
+			memcpy(newopt, inet_opt, sizeof(*inet_opt) +
+			       inet_opt->opt.optlen);
+		else
+			pr_err("%s: Failed to copy ip options\n", __func__);
+	}
+	RCU_INIT_POINTER(newinet->inet_opt, newopt);
+	rcu_read_unlock();
+}
+
+/* Account for the IP options */
+static int sctp_v4_ip_options_len(struct sock *sk)
+{
+	struct inet_sock *inet = inet_sk(sk);
+
+	if (inet->inet_opt)
+		return inet->inet_opt->opt.optlen;
+	else
+		return 0;
+}
+
 /* Initialize a sctp_addr from in incoming skb.  */
 static void sctp_v4_from_skb(union sctp_addr *addr, struct sk_buff *skb,
 			     int is_saddr)
@@ -588,6 +622,8 @@  static struct sock *sctp_v4_create_accept_sk(struct sock *sk,
 	sctp_copy_sock(newsk, sk, asoc);
 	sock_reset_flag(newsk, SOCK_ZAPPED);
 
+	sctp_v4_copy_ip_options(sk, newsk);
+
 	newinet = inet_sk(newsk);
 
 	newinet->inet_daddr = asoc->peer.primary_addr.v4.sin_addr.s_addr;
@@ -1006,6 +1042,7 @@  static struct sctp_pf sctp_pf_inet = {
 	.addr_to_user  = sctp_v4_addr_to_user,
 	.to_sk_saddr   = sctp_v4_to_sk_saddr,
 	.to_sk_daddr   = sctp_v4_to_sk_daddr,
+	.copy_ip_options = sctp_v4_copy_ip_options,
 	.af            = &sctp_af_inet
 };
 
@@ -1090,6 +1127,7 @@  static struct sctp_af sctp_af_inet = {
 	.ecn_capable	   = sctp_v4_ecn_capable,
 	.net_header_len	   = sizeof(struct iphdr),
 	.sockaddr_len	   = sizeof(struct sockaddr_in),
+	.ip_options_len	   = sctp_v4_ip_options_len,
 #ifdef CONFIG_COMPAT
 	.compat_setsockopt = compat_ip_setsockopt,
 	.compat_getsockopt = compat_ip_getsockopt,
diff --git a/net/sctp/socket.c b/net/sctp/socket.c
index bf271f8..eb55c63 100644
--- a/net/sctp/socket.c
+++ b/net/sctp/socket.c
@@ -3138,6 +3138,7 @@  static int sctp_setsockopt_mappedv4(struct sock *sk, char __user *optval, unsign
 static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned int optlen)
 {
 	struct sctp_sock *sp = sctp_sk(sk);
+	struct sctp_af *af = sp->pf->af;
 	struct sctp_assoc_value params;
 	struct sctp_association *asoc;
 	int val;
@@ -3162,7 +3163,8 @@  static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned
 	if (val) {
 		int min_len, max_len;
 
-		min_len = SCTP_DEFAULT_MINSEGMENT - sp->pf->af->net_header_len;
+		min_len = SCTP_DEFAULT_MINSEGMENT - af->net_header_len;
+		min_len -= af->ip_options_len(sk);
 		min_len -= sizeof(struct sctphdr) +
 			   sizeof(struct sctp_data_chunk);
 
@@ -3175,7 +3177,8 @@  static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned
 	asoc = sctp_id2assoc(sk, params.assoc_id);
 	if (asoc) {
 		if (val == 0) {
-			val = asoc->pathmtu - sp->pf->af->net_header_len;
+			val = asoc->pathmtu - af->net_header_len;
+			val -= af->ip_options_len(sk);
 			val -= sizeof(struct sctphdr) +
 			       sctp_datachk_len(&asoc->stream);
 		}
@@ -5087,9 +5090,11 @@  int sctp_do_peeloff(struct sock *sk, sctp_assoc_t id, struct socket **sockp)
 	sctp_copy_sock(sock->sk, sk, asoc);
 
 	/* Make peeled-off sockets more like 1-1 accepted sockets.
-	 * Set the daddr and initialize id to something more random
+	 * Set the daddr and initialize id to something more random and also
+	 * copy over any ip options.
 	 */
 	sp->pf->to_sk_daddr(&asoc->peer.primary_addr, sk);
+	sp->pf->copy_ip_options(sk, sock->sk);
 
 	/* Populate the fields of the newsk from the oldsk and migrate the
 	 * asoc to the newsk.