Commit 4422cc0d authored by David S. Miller's avatar David S. Miller

Merge branch 'sctp-refactor-MTU-handling'

Marcelo Ricardo Leitner says:

====================
sctp: refactor MTU handling

Currently MTU handling is spread over SCTP stack. There are multiple
places doing same/similar calculations and updating them is error prone
as one spot can easily be left out.

This patchset converges it into a more concise and consistent code. In
general, it moves MTU handling from functions with bigger objectives,
such as sctp_assoc_add_peer(), to specific functions.

It's also a preparation for the next patchset, which removes the
duplication between sctp_make_op_error_space and
sctp_make_op_error_fixed and relies on sctp_mtu_payload introduced here.

More details on each patch.
====================
Reviewed-by: default avatarXin Long <lucien.xin@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 5a643c86 38687b56
......@@ -254,11 +254,10 @@ enum { SCTP_ARBITRARY_COOKIE_ECHO_LEN = 200 };
#define SCTP_TSN_MAP_SIZE 4096
/* We will not record more than this many duplicate TSNs between two
* SACKs. The minimum PMTU is 576. Remove all the headers and there
* is enough room for 131 duplicate reports. Round down to the
* SACKs. The minimum PMTU is 512. Remove all the headers and there
* is enough room for 117 duplicate reports. Round down to the
* nearest power of 2.
*/
enum { SCTP_MIN_PMTU = 576 };
enum { SCTP_MAX_DUP_TSNS = 16 };
enum { SCTP_MAX_GABS = 16 };
......
......@@ -428,32 +428,6 @@ static inline int sctp_list_single_entry(struct list_head *head)
return (head->next != head) && (head->next == head->prev);
}
/* Break down data chunks at this point. */
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 -= 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)
frag = min_t(int, frag, asoc->user_frag);
frag = SCTP_TRUNC4(min_t(int, frag, SCTP_MAX_CHUNK_LEN -
sctp_datachk_len(&asoc->stream)));
return frag;
}
static inline void sctp_assoc_pending_pmtu(struct sctp_association *asoc)
{
sctp_assoc_sync_pmtu(asoc);
asoc->pmtu_pending = 0;
}
static inline bool sctp_chunk_pending(const struct sctp_chunk *chunk)
{
return !list_empty(&chunk->list);
......@@ -607,17 +581,29 @@ static inline struct dst_entry *sctp_transport_dst_check(struct sctp_transport *
return t->dst;
}
static inline bool sctp_transport_pmtu_check(struct sctp_transport *t)
/* Calculate max payload size given a MTU, or the total overhead if
* given MTU is zero
*/
static inline __u32 sctp_mtu_payload(const struct sctp_sock *sp,
__u32 mtu, __u32 extra)
{
__u32 pmtu = max_t(size_t, SCTP_TRUNC4(dst_mtu(t->dst)),
SCTP_DEFAULT_MINSEGMENT);
__u32 overhead = sizeof(struct sctphdr) + extra;
if (t->pathmtu == pmtu)
return true;
if (sp)
overhead += sp->pf->af->net_header_len;
else
overhead += sizeof(struct ipv6hdr);
t->pathmtu = pmtu;
if (WARN_ON_ONCE(mtu && mtu <= overhead))
mtu = overhead;
return false;
return mtu ? mtu - overhead : overhead;
}
static inline __u32 sctp_dst_mtu(const struct dst_entry *dst)
{
return SCTP_TRUNC4(max_t(__u32, dst_mtu(dst),
SCTP_DEFAULT_MINSEGMENT));
}
#endif /* __net_sctp_h__ */
......@@ -2097,6 +2097,8 @@ int sctp_assoc_update(struct sctp_association *old,
__u32 sctp_association_get_next_tsn(struct sctp_association *);
void sctp_assoc_update_frag_point(struct sctp_association *asoc);
void sctp_assoc_set_pmtu(struct sctp_association *asoc, __u32 pmtu);
void sctp_assoc_sync_pmtu(struct sctp_association *asoc);
void sctp_assoc_rwnd_increase(struct sctp_association *, unsigned int);
void sctp_assoc_rwnd_decrease(struct sctp_association *, unsigned int);
......
......@@ -652,33 +652,20 @@ struct sctp_transport *sctp_assoc_add_peer(struct sctp_association *asoc,
*/
peer->param_flags = asoc->param_flags;
sctp_transport_route(peer, NULL, sp);
/* Initialize the pmtu of the transport. */
if (peer->param_flags & SPP_PMTUD_DISABLE) {
if (asoc->pathmtu)
peer->pathmtu = asoc->pathmtu;
else
peer->pathmtu = SCTP_DEFAULT_MAXSEGMENT;
}
sctp_transport_route(peer, NULL, sp);
/* If this is the first transport addr on this association,
* initialize the association PMTU to the peer's PMTU.
* If not and the current association PMTU is higher than the new
* peer's PMTU, reset the association PMTU to the new peer's PMTU.
*/
if (asoc->pathmtu)
asoc->pathmtu = min_t(int, peer->pathmtu, asoc->pathmtu);
else
asoc->pathmtu = peer->pathmtu;
pr_debug("%s: association:%p PMTU set to %d\n", __func__, asoc,
asoc->pathmtu);
sctp_assoc_set_pmtu(asoc, asoc->pathmtu ?
min_t(int, peer->pathmtu, asoc->pathmtu) :
peer->pathmtu);
peer->pmtu_pending = 0;
asoc->frag_point = sctp_frag_point(asoc, asoc->pathmtu);
/* The asoc->peer.port might not be meaningful yet, but
* initialize the packet structure anyway.
*/
......@@ -1381,6 +1368,31 @@ sctp_assoc_choose_alter_transport(struct sctp_association *asoc,
}
}
void sctp_assoc_update_frag_point(struct sctp_association *asoc)
{
int frag = sctp_mtu_payload(sctp_sk(asoc->base.sk), asoc->pathmtu,
sctp_datachk_len(&asoc->stream));
if (asoc->user_frag)
frag = min_t(int, frag, asoc->user_frag);
frag = min_t(int, frag, SCTP_MAX_CHUNK_LEN -
sctp_datachk_len(&asoc->stream));
asoc->frag_point = SCTP_TRUNC4(frag);
}
void sctp_assoc_set_pmtu(struct sctp_association *asoc, __u32 pmtu)
{
if (asoc->pathmtu != pmtu) {
asoc->pathmtu = pmtu;
sctp_assoc_update_frag_point(asoc);
}
pr_debug("%s: asoc:%p, pmtu:%d, frag_point:%d\n", __func__, asoc,
asoc->pathmtu, asoc->frag_point);
}
/* Update the association's pmtu and frag_point by going through all the
* transports. This routine is called when a transport's PMTU has changed.
*/
......@@ -1393,24 +1405,16 @@ void sctp_assoc_sync_pmtu(struct sctp_association *asoc)
return;
/* Get the lowest pmtu of all the transports. */
list_for_each_entry(t, &asoc->peer.transport_addr_list,
transports) {
list_for_each_entry(t, &asoc->peer.transport_addr_list, transports) {
if (t->pmtu_pending && t->dst) {
sctp_transport_update_pmtu(
t, SCTP_TRUNC4(dst_mtu(t->dst)));
sctp_transport_update_pmtu(t, sctp_dst_mtu(t->dst));
t->pmtu_pending = 0;
}
if (!pmtu || (t->pathmtu < pmtu))
pmtu = t->pathmtu;
}
if (pmtu) {
asoc->pathmtu = pmtu;
asoc->frag_point = sctp_frag_point(asoc, pmtu);
}
pr_debug("%s: asoc:%p, pmtu:%d, frag_point:%d\n", __func__, asoc,
asoc->pathmtu, asoc->frag_point);
sctp_assoc_set_pmtu(asoc, pmtu);
}
/* Should we send a SACK to update our peer? */
......
......@@ -172,8 +172,6 @@ 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);
......@@ -192,12 +190,7 @@ struct sctp_datamsg *sctp_datamsg_from_user(struct sctp_association *asoc,
/* This is the biggest possible DATA chunk that can fit into
* the packet
*/
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);
max_data = asoc->frag_point;
/* If the the peer requested that we authenticate DATA chunks
* we need to account for bundling of the AUTH chunks along with
......@@ -222,9 +215,6 @@ struct sctp_datamsg *sctp_datamsg_from_user(struct sctp_association *asoc,
}
}
/* Check what's our max considering the above */
max_data = min_t(size_t, max_data, asoc->frag_point);
/* Set first_len and then account for possible bundles on first frag */
first_len = max_data;
......
......@@ -90,8 +90,8 @@ void sctp_packet_config(struct sctp_packet *packet, __u32 vtag,
{
struct sctp_transport *tp = packet->transport;
struct sctp_association *asoc = tp->asoc;
struct sctp_sock *sp = NULL;
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;
......@@ -102,28 +102,20 @@ void sctp_packet_config(struct sctp_packet *packet, __u32 vtag,
/* set packet max_size with pathmtu, then calculate overhead */
packet->max_size = tp->pathmtu;
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;
sk = asoc->base.sk;
sp = sctp_sk(sk);
}
packet->overhead = sctp_mtu_payload(sp, 0, 0);
packet->size = packet->overhead;
if (!asoc)
return;
/* update dst or transport pathmtu if in need */
sk = asoc->base.sk;
if (!sctp_transport_dst_check(tp)) {
sctp_transport_route(tp, NULL, sctp_sk(sk));
if (asoc->param_flags & SPP_PMTUD_ENABLE)
sctp_assoc_sync_pmtu(asoc);
} else if (!sctp_transport_pmtu_check(tp)) {
sctp_transport_route(tp, NULL, sp);
if (asoc->param_flags & SPP_PMTUD_ENABLE)
sctp_assoc_sync_pmtu(asoc);
}
......
......@@ -644,16 +644,15 @@ static int sctp_send_asconf_add_ip(struct sock *sk,
list_for_each_entry(trans,
&asoc->peer.transport_addr_list, transports) {
/* Clear the source and route cache */
sctp_transport_dst_release(trans);
trans->cwnd = min(4*asoc->pathmtu, max_t(__u32,
2*asoc->pathmtu, 4380));
trans->ssthresh = asoc->peer.i.a_rwnd;
trans->rto = asoc->rto_initial;
sctp_max_rto(asoc, trans);
trans->rtt = trans->srtt = trans->rttvar = 0;
/* Clear the source and route cache */
sctp_transport_route(trans, NULL,
sctp_sk(asoc->base.sk));
sctp_sk(asoc->base.sk));
}
}
retval = sctp_send_asconf(asoc, chunk);
......@@ -896,7 +895,6 @@ static int sctp_send_asconf_del_ip(struct sock *sk,
*/
list_for_each_entry(transport, &asoc->peer.transport_addr_list,
transports) {
sctp_transport_dst_release(transport);
sctp_transport_route(transport, NULL,
sctp_sk(asoc->base.sk));
}
......@@ -1895,6 +1893,7 @@ static int sctp_sendmsg_to_asoc(struct sctp_association *asoc,
struct sctp_sndrcvinfo *sinfo)
{
struct sock *sk = asoc->base.sk;
struct sctp_sock *sp = sctp_sk(sk);
struct net *net = sock_net(sk);
struct sctp_datamsg *datamsg;
bool wait_connect = false;
......@@ -1913,13 +1912,16 @@ static int sctp_sendmsg_to_asoc(struct sctp_association *asoc,
goto err;
}
if (sctp_sk(sk)->disable_fragments && msg_len > asoc->frag_point) {
if (sp->disable_fragments && msg_len > asoc->frag_point) {
err = -EMSGSIZE;
goto err;
}
if (asoc->pmtu_pending)
sctp_assoc_pending_pmtu(asoc);
if (asoc->pmtu_pending) {
if (sp->param_flags & SPP_PMTUD_ENABLE)
sctp_assoc_sync_pmtu(asoc);
asoc->pmtu_pending = 0;
}
if (sctp_wspace(asoc) < msg_len)
sctp_prsctp_prune(asoc, sinfo, msg_len - sctp_wspace(asoc));
......@@ -1936,7 +1938,7 @@ static int sctp_sendmsg_to_asoc(struct sctp_association *asoc,
if (err)
goto err;
if (sctp_sk(sk)->strm_interleave) {
if (sp->strm_interleave) {
timeo = sock_sndtimeo(sk, 0);
err = sctp_wait_for_connect(asoc, &timeo);
if (err)
......@@ -2539,7 +2541,7 @@ static int sctp_apply_peer_addr_params(struct sctp_paddrparams *params,
trans->pathmtu = params->spp_pathmtu;
sctp_assoc_sync_pmtu(asoc);
} else if (asoc) {
asoc->pathmtu = params->spp_pathmtu;
sctp_assoc_set_pmtu(asoc, params->spp_pathmtu);
} else {
sp->pathmtu = params->spp_pathmtu;
}
......@@ -3209,7 +3211,6 @@ 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;
......@@ -3231,30 +3232,24 @@ static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned
return -EINVAL;
}
asoc = sctp_id2assoc(sk, params.assoc_id);
if (val) {
int min_len, max_len;
__u16 datasize = asoc ? sctp_datachk_len(&asoc->stream) :
sizeof(struct sctp_data_chunk);
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);
max_len = SCTP_MAX_CHUNK_LEN - sizeof(struct sctp_data_chunk);
min_len = sctp_mtu_payload(sp, SCTP_DEFAULT_MINSEGMENT,
datasize);
max_len = SCTP_MAX_CHUNK_LEN - datasize;
if (val < min_len || val > max_len)
return -EINVAL;
}
asoc = sctp_id2assoc(sk, params.assoc_id);
if (asoc) {
if (val == 0) {
val = asoc->pathmtu - af->net_header_len;
val -= af->ip_options_len(sk);
val -= sizeof(struct sctphdr) +
sctp_datachk_len(&asoc->stream);
}
asoc->user_frag = val;
asoc->frag_point = sctp_frag_point(asoc, asoc->pathmtu);
sctp_assoc_update_frag_point(asoc);
} else {
if (params.assoc_id && sctp_style(sk, UDP))
return -EINVAL;
......
......@@ -242,9 +242,18 @@ void sctp_transport_pmtu(struct sctp_transport *transport, struct sock *sk)
&transport->fl, sk);
}
if (transport->dst) {
transport->pathmtu = SCTP_TRUNC4(dst_mtu(transport->dst));
} else
if (transport->param_flags & SPP_PMTUD_DISABLE) {
struct sctp_association *asoc = transport->asoc;
if (!transport->pathmtu && asoc && asoc->pathmtu)
transport->pathmtu = asoc->pathmtu;
if (transport->pathmtu)
return;
}
if (transport->dst)
transport->pathmtu = sctp_dst_mtu(transport->dst);
else
transport->pathmtu = SCTP_DEFAULT_MAXSEGMENT;
}
......@@ -290,6 +299,7 @@ void sctp_transport_route(struct sctp_transport *transport,
struct sctp_association *asoc = transport->asoc;
struct sctp_af *af = transport->af_specific;
sctp_transport_dst_release(transport);
af->get_dst(transport, saddr, &transport->fl, sctp_opt2sk(opt));
if (saddr)
......@@ -297,21 +307,14 @@ void sctp_transport_route(struct sctp_transport *transport,
else
af->get_saddr(opt, transport, &transport->fl);
if ((transport->param_flags & SPP_PMTUD_DISABLE) && transport->pathmtu) {
return;
}
if (transport->dst) {
transport->pathmtu = SCTP_TRUNC4(dst_mtu(transport->dst));
sctp_transport_pmtu(transport, sctp_opt2sk(opt));
/* Initialize sk->sk_rcv_saddr, if the transport is the
* association's active path for getsockname().
*/
if (asoc && (!asoc->peer.primary_path ||
(transport == asoc->peer.active_path)))
opt->pf->to_sk_saddr(&transport->saddr,
asoc->base.sk);
} else
transport->pathmtu = SCTP_DEFAULT_MAXSEGMENT;
/* Initialize sk->sk_rcv_saddr, if the transport is the
* association's active path for getsockname().
*/
if (transport->dst && asoc &&
(!asoc->peer.primary_path || transport == asoc->peer.active_path))
opt->pf->to_sk_saddr(&transport->saddr, asoc->base.sk);
}
/* Hold a reference to a transport. */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment