1457c8996SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
241d73ec0SPatrick McHardy #include <linux/types.h>
341d73ec0SPatrick McHardy #include <linux/netfilter.h>
441d73ec0SPatrick McHardy #include <net/tcp.h>
541d73ec0SPatrick McHardy
641d73ec0SPatrick McHardy #include <net/netfilter/nf_conntrack.h>
741d73ec0SPatrick McHardy #include <net/netfilter/nf_conntrack_extend.h>
841d73ec0SPatrick McHardy #include <net/netfilter/nf_conntrack_seqadj.h>
941d73ec0SPatrick McHardy
nf_ct_seqadj_init(struct nf_conn * ct,enum ip_conntrack_info ctinfo,s32 off)1048b1de4cSPatrick McHardy int nf_ct_seqadj_init(struct nf_conn *ct, enum ip_conntrack_info ctinfo,
1148b1de4cSPatrick McHardy s32 off)
1248b1de4cSPatrick McHardy {
1348b1de4cSPatrick McHardy enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
1448b1de4cSPatrick McHardy struct nf_conn_seqadj *seqadj;
1548b1de4cSPatrick McHardy struct nf_ct_seqadj *this_way;
1648b1de4cSPatrick McHardy
1748b1de4cSPatrick McHardy if (off == 0)
1848b1de4cSPatrick McHardy return 0;
1948b1de4cSPatrick McHardy
2048b1de4cSPatrick McHardy set_bit(IPS_SEQ_ADJUST_BIT, &ct->status);
2148b1de4cSPatrick McHardy
2248b1de4cSPatrick McHardy seqadj = nfct_seqadj(ct);
2348b1de4cSPatrick McHardy this_way = &seqadj->seq[dir];
2448b1de4cSPatrick McHardy this_way->offset_before = off;
2548b1de4cSPatrick McHardy this_way->offset_after = off;
2648b1de4cSPatrick McHardy return 0;
2748b1de4cSPatrick McHardy }
2848b1de4cSPatrick McHardy EXPORT_SYMBOL_GPL(nf_ct_seqadj_init);
2948b1de4cSPatrick McHardy
nf_ct_seqadj_set(struct nf_conn * ct,enum ip_conntrack_info ctinfo,__be32 seq,s32 off)3041d73ec0SPatrick McHardy int nf_ct_seqadj_set(struct nf_conn *ct, enum ip_conntrack_info ctinfo,
3141d73ec0SPatrick McHardy __be32 seq, s32 off)
3241d73ec0SPatrick McHardy {
3341d73ec0SPatrick McHardy struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
3441d73ec0SPatrick McHardy enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
3541d73ec0SPatrick McHardy struct nf_ct_seqadj *this_way;
3641d73ec0SPatrick McHardy
3741d73ec0SPatrick McHardy if (off == 0)
3841d73ec0SPatrick McHardy return 0;
3941d73ec0SPatrick McHardy
40db12cf27SJesper Dangaard Brouer if (unlikely(!seqadj)) {
41f2661adcSJesper Dangaard Brouer WARN_ONCE(1, "Missing nfct_seqadj_ext_add() setup call\n");
42db12cf27SJesper Dangaard Brouer return 0;
43db12cf27SJesper Dangaard Brouer }
44db12cf27SJesper Dangaard Brouer
4541d73ec0SPatrick McHardy set_bit(IPS_SEQ_ADJUST_BIT, &ct->status);
4641d73ec0SPatrick McHardy
4741d73ec0SPatrick McHardy spin_lock_bh(&ct->lock);
4841d73ec0SPatrick McHardy this_way = &seqadj->seq[dir];
4941d73ec0SPatrick McHardy if (this_way->offset_before == this_way->offset_after ||
5023dfe136SPhil Oester before(this_way->correction_pos, ntohl(seq))) {
5123dfe136SPhil Oester this_way->correction_pos = ntohl(seq);
5241d73ec0SPatrick McHardy this_way->offset_before = this_way->offset_after;
5341d73ec0SPatrick McHardy this_way->offset_after += off;
5441d73ec0SPatrick McHardy }
5541d73ec0SPatrick McHardy spin_unlock_bh(&ct->lock);
5641d73ec0SPatrick McHardy return 0;
5741d73ec0SPatrick McHardy }
5841d73ec0SPatrick McHardy EXPORT_SYMBOL_GPL(nf_ct_seqadj_set);
5941d73ec0SPatrick McHardy
nf_ct_tcp_seqadj_set(struct sk_buff * skb,struct nf_conn * ct,enum ip_conntrack_info ctinfo,s32 off)6041d73ec0SPatrick McHardy void nf_ct_tcp_seqadj_set(struct sk_buff *skb,
6141d73ec0SPatrick McHardy struct nf_conn *ct, enum ip_conntrack_info ctinfo,
6241d73ec0SPatrick McHardy s32 off)
6341d73ec0SPatrick McHardy {
6441d73ec0SPatrick McHardy const struct tcphdr *th;
6541d73ec0SPatrick McHardy
6641d73ec0SPatrick McHardy if (nf_ct_protonum(ct) != IPPROTO_TCP)
6741d73ec0SPatrick McHardy return;
6841d73ec0SPatrick McHardy
6941d73ec0SPatrick McHardy th = (struct tcphdr *)(skb_network_header(skb) + ip_hdrlen(skb));
7041d73ec0SPatrick McHardy nf_ct_seqadj_set(ct, ctinfo, th->seq, off);
7141d73ec0SPatrick McHardy }
7241d73ec0SPatrick McHardy EXPORT_SYMBOL_GPL(nf_ct_tcp_seqadj_set);
7341d73ec0SPatrick McHardy
7441d73ec0SPatrick McHardy /* Adjust one found SACK option including checksum correction */
nf_ct_sack_block_adjust(struct sk_buff * skb,struct tcphdr * tcph,unsigned int sackoff,unsigned int sackend,struct nf_ct_seqadj * seq)7541d73ec0SPatrick McHardy static void nf_ct_sack_block_adjust(struct sk_buff *skb,
7641d73ec0SPatrick McHardy struct tcphdr *tcph,
7741d73ec0SPatrick McHardy unsigned int sackoff,
7841d73ec0SPatrick McHardy unsigned int sackend,
7941d73ec0SPatrick McHardy struct nf_ct_seqadj *seq)
8041d73ec0SPatrick McHardy {
8141d73ec0SPatrick McHardy while (sackoff < sackend) {
8241d73ec0SPatrick McHardy struct tcp_sack_block_wire *sack;
8341d73ec0SPatrick McHardy __be32 new_start_seq, new_end_seq;
8441d73ec0SPatrick McHardy
8541d73ec0SPatrick McHardy sack = (void *)skb->data + sackoff;
8641d73ec0SPatrick McHardy if (after(ntohl(sack->start_seq) - seq->offset_before,
8741d73ec0SPatrick McHardy seq->correction_pos))
8841d73ec0SPatrick McHardy new_start_seq = htonl(ntohl(sack->start_seq) -
8941d73ec0SPatrick McHardy seq->offset_after);
9041d73ec0SPatrick McHardy else
9141d73ec0SPatrick McHardy new_start_seq = htonl(ntohl(sack->start_seq) -
9241d73ec0SPatrick McHardy seq->offset_before);
9341d73ec0SPatrick McHardy
9441d73ec0SPatrick McHardy if (after(ntohl(sack->end_seq) - seq->offset_before,
9541d73ec0SPatrick McHardy seq->correction_pos))
9641d73ec0SPatrick McHardy new_end_seq = htonl(ntohl(sack->end_seq) -
9741d73ec0SPatrick McHardy seq->offset_after);
9841d73ec0SPatrick McHardy else
9941d73ec0SPatrick McHardy new_end_seq = htonl(ntohl(sack->end_seq) -
10041d73ec0SPatrick McHardy seq->offset_before);
10141d73ec0SPatrick McHardy
102b44b565cSGao feng pr_debug("sack_adjust: start_seq: %u->%u, end_seq: %u->%u\n",
103b44b565cSGao feng ntohl(sack->start_seq), ntohl(new_start_seq),
104b44b565cSGao feng ntohl(sack->end_seq), ntohl(new_end_seq));
10541d73ec0SPatrick McHardy
10641d73ec0SPatrick McHardy inet_proto_csum_replace4(&tcph->check, skb,
1074b048d6dSTom Herbert sack->start_seq, new_start_seq, false);
10841d73ec0SPatrick McHardy inet_proto_csum_replace4(&tcph->check, skb,
1094b048d6dSTom Herbert sack->end_seq, new_end_seq, false);
11041d73ec0SPatrick McHardy sack->start_seq = new_start_seq;
11141d73ec0SPatrick McHardy sack->end_seq = new_end_seq;
11241d73ec0SPatrick McHardy sackoff += sizeof(*sack);
11341d73ec0SPatrick McHardy }
11441d73ec0SPatrick McHardy }
11541d73ec0SPatrick McHardy
11641d73ec0SPatrick McHardy /* TCP SACK sequence number adjustment */
nf_ct_sack_adjust(struct sk_buff * skb,unsigned int protoff,struct nf_conn * ct,enum ip_conntrack_info ctinfo)11741d73ec0SPatrick McHardy static unsigned int nf_ct_sack_adjust(struct sk_buff *skb,
11841d73ec0SPatrick McHardy unsigned int protoff,
11941d73ec0SPatrick McHardy struct nf_conn *ct,
12041d73ec0SPatrick McHardy enum ip_conntrack_info ctinfo)
12141d73ec0SPatrick McHardy {
122530aad77SFlorian Westphal struct tcphdr *tcph = (void *)skb->data + protoff;
12341d73ec0SPatrick McHardy struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
124530aad77SFlorian Westphal unsigned int dir, optoff, optend;
12541d73ec0SPatrick McHardy
12641d73ec0SPatrick McHardy optoff = protoff + sizeof(struct tcphdr);
12741d73ec0SPatrick McHardy optend = protoff + tcph->doff * 4;
12841d73ec0SPatrick McHardy
12986f04538SFlorian Westphal if (skb_ensure_writable(skb, optend))
13041d73ec0SPatrick McHardy return 0;
13141d73ec0SPatrick McHardy
132530aad77SFlorian Westphal tcph = (void *)skb->data + protoff;
13341d73ec0SPatrick McHardy dir = CTINFO2DIR(ctinfo);
13441d73ec0SPatrick McHardy
13541d73ec0SPatrick McHardy while (optoff < optend) {
13641d73ec0SPatrick McHardy /* Usually: option, length. */
13741d73ec0SPatrick McHardy unsigned char *op = skb->data + optoff;
13841d73ec0SPatrick McHardy
13941d73ec0SPatrick McHardy switch (op[0]) {
14041d73ec0SPatrick McHardy case TCPOPT_EOL:
14141d73ec0SPatrick McHardy return 1;
14241d73ec0SPatrick McHardy case TCPOPT_NOP:
14341d73ec0SPatrick McHardy optoff++;
14441d73ec0SPatrick McHardy continue;
14541d73ec0SPatrick McHardy default:
14641d73ec0SPatrick McHardy /* no partial options */
14741d73ec0SPatrick McHardy if (optoff + 1 == optend ||
14841d73ec0SPatrick McHardy optoff + op[1] > optend ||
14941d73ec0SPatrick McHardy op[1] < 2)
15041d73ec0SPatrick McHardy return 0;
15141d73ec0SPatrick McHardy if (op[0] == TCPOPT_SACK &&
15241d73ec0SPatrick McHardy op[1] >= 2+TCPOLEN_SACK_PERBLOCK &&
15341d73ec0SPatrick McHardy ((op[1] - 2) % TCPOLEN_SACK_PERBLOCK) == 0)
15441d73ec0SPatrick McHardy nf_ct_sack_block_adjust(skb, tcph, optoff + 2,
15541d73ec0SPatrick McHardy optoff+op[1],
15641d73ec0SPatrick McHardy &seqadj->seq[!dir]);
15741d73ec0SPatrick McHardy optoff += op[1];
15841d73ec0SPatrick McHardy }
15941d73ec0SPatrick McHardy }
16041d73ec0SPatrick McHardy return 1;
16141d73ec0SPatrick McHardy }
16241d73ec0SPatrick McHardy
16341d73ec0SPatrick McHardy /* TCP sequence number adjustment. Returns 1 on success, 0 on failure */
nf_ct_seq_adjust(struct sk_buff * skb,struct nf_conn * ct,enum ip_conntrack_info ctinfo,unsigned int protoff)16441d73ec0SPatrick McHardy int nf_ct_seq_adjust(struct sk_buff *skb,
16541d73ec0SPatrick McHardy struct nf_conn *ct, enum ip_conntrack_info ctinfo,
16641d73ec0SPatrick McHardy unsigned int protoff)
16741d73ec0SPatrick McHardy {
16841d73ec0SPatrick McHardy enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
16941d73ec0SPatrick McHardy struct tcphdr *tcph;
17041d73ec0SPatrick McHardy __be32 newseq, newack;
17141d73ec0SPatrick McHardy s32 seqoff, ackoff;
17241d73ec0SPatrick McHardy struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
17341d73ec0SPatrick McHardy struct nf_ct_seqadj *this_way, *other_way;
1748d11350fSGao Feng int res = 1;
17541d73ec0SPatrick McHardy
17641d73ec0SPatrick McHardy this_way = &seqadj->seq[dir];
17741d73ec0SPatrick McHardy other_way = &seqadj->seq[!dir];
17841d73ec0SPatrick McHardy
17986f04538SFlorian Westphal if (skb_ensure_writable(skb, protoff + sizeof(*tcph)))
18041d73ec0SPatrick McHardy return 0;
18141d73ec0SPatrick McHardy
18241d73ec0SPatrick McHardy tcph = (void *)skb->data + protoff;
18341d73ec0SPatrick McHardy spin_lock_bh(&ct->lock);
18441d73ec0SPatrick McHardy if (after(ntohl(tcph->seq), this_way->correction_pos))
18541d73ec0SPatrick McHardy seqoff = this_way->offset_after;
18641d73ec0SPatrick McHardy else
18741d73ec0SPatrick McHardy seqoff = this_way->offset_before;
18841d73ec0SPatrick McHardy
1898d11350fSGao Feng newseq = htonl(ntohl(tcph->seq) + seqoff);
1908d11350fSGao Feng inet_proto_csum_replace4(&tcph->check, skb, tcph->seq, newseq, false);
1918d11350fSGao Feng pr_debug("Adjusting sequence number from %u->%u\n",
1928d11350fSGao Feng ntohl(tcph->seq), ntohl(newseq));
1938d11350fSGao Feng tcph->seq = newseq;
1948d11350fSGao Feng
1958d11350fSGao Feng if (!tcph->ack)
1968d11350fSGao Feng goto out;
1978d11350fSGao Feng
19841d73ec0SPatrick McHardy if (after(ntohl(tcph->ack_seq) - other_way->offset_before,
19941d73ec0SPatrick McHardy other_way->correction_pos))
20041d73ec0SPatrick McHardy ackoff = other_way->offset_after;
20141d73ec0SPatrick McHardy else
20241d73ec0SPatrick McHardy ackoff = other_way->offset_before;
20341d73ec0SPatrick McHardy
20441d73ec0SPatrick McHardy newack = htonl(ntohl(tcph->ack_seq) - ackoff);
2054b048d6dSTom Herbert inet_proto_csum_replace4(&tcph->check, skb, tcph->ack_seq, newack,
2064b048d6dSTom Herbert false);
2078d11350fSGao Feng pr_debug("Adjusting ack number from %u->%u, ack from %u->%u\n",
20841d73ec0SPatrick McHardy ntohl(tcph->seq), ntohl(newseq), ntohl(tcph->ack_seq),
20941d73ec0SPatrick McHardy ntohl(newack));
21041d73ec0SPatrick McHardy tcph->ack_seq = newack;
21141d73ec0SPatrick McHardy
212530aad77SFlorian Westphal res = nf_ct_sack_adjust(skb, protoff, ct, ctinfo);
2138d11350fSGao Feng out:
21441d73ec0SPatrick McHardy spin_unlock_bh(&ct->lock);
21541d73ec0SPatrick McHardy
21641d73ec0SPatrick McHardy return res;
21741d73ec0SPatrick McHardy }
21841d73ec0SPatrick McHardy EXPORT_SYMBOL_GPL(nf_ct_seq_adjust);
21941d73ec0SPatrick McHardy
nf_ct_seq_offset(const struct nf_conn * ct,enum ip_conntrack_dir dir,u32 seq)22041d73ec0SPatrick McHardy s32 nf_ct_seq_offset(const struct nf_conn *ct,
22141d73ec0SPatrick McHardy enum ip_conntrack_dir dir,
22241d73ec0SPatrick McHardy u32 seq)
22341d73ec0SPatrick McHardy {
22441d73ec0SPatrick McHardy struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
22541d73ec0SPatrick McHardy struct nf_ct_seqadj *this_way;
22641d73ec0SPatrick McHardy
22741d73ec0SPatrick McHardy if (!seqadj)
22841d73ec0SPatrick McHardy return 0;
22941d73ec0SPatrick McHardy
23041d73ec0SPatrick McHardy this_way = &seqadj->seq[dir];
23141d73ec0SPatrick McHardy return after(seq, this_way->correction_pos) ?
23241d73ec0SPatrick McHardy this_way->offset_after : this_way->offset_before;
23341d73ec0SPatrick McHardy }
23441d73ec0SPatrick McHardy EXPORT_SYMBOL_GPL(nf_ct_seq_offset);
235