1 /* $KAME: ip_ecn.c,v 1.12 2002/01/07 11:34:47 kjc Exp $ */
2
3 /*-
4 * SPDX-License-Identifier: BSD-3-Clause
5 *
6 * Copyright (C) 1999 WIDE Project.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the project nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 *
33 */
34
35 #include "opt_inet.h"
36 #include "opt_inet6.h"
37
38 #include <sys/param.h>
39 #include <sys/systm.h>
40 #include <sys/mbuf.h>
41 #include <sys/errno.h>
42
43 #include <netinet/in.h>
44 #include <netinet/in_systm.h>
45 #include <netinet/ip.h>
46 #ifdef INET6
47 #include <netinet/ip6.h>
48 #endif
49
50 #include <netinet/ip_ecn.h>
51 #ifdef INET6
52 #include <netinet6/ip6_ecn.h>
53 #endif
54
55 /*
56 * ECN and TOS (or TCLASS) processing rules at tunnel encapsulation and
57 * decapsulation from RFC6040:
58 *
59 * Outer Hdr at Inner Hdr at
60 * Encapsulator Decapsulator
61 * Header fields: -------------------- ------------
62 * DS Field copied from inner hdr no change
63 * ECN Field constructed by (I) constructed by (E)
64 *
65 * ECN_ALLOWED (normal mode):
66 * (I) copy the ECN field to the outer header.
67 *
68 * (E) if the ECN field in the outer header is set to CE and the ECN
69 * field of the inner header is not-ECT, drop the packet.
70 * If the ECN field in the inner header is set to ECT(0) and the ECN
71 * field in the outer header is set to ECT(1), copy ECT(1) to
72 * the inner header. If the ECN field in the inner header is set
73 * to ECT(0) or ECT(1) and the ECN field in the outer header is set to
74 * CE, copy CE to the inner header.
75 * Otherwise, make no change to the inner header. This behaviour can be
76 * summarized in the table below:
77 *
78 * Outer Header at Decapsulator
79 * +---------+------------+------------+------------+
80 * | Not-ECT | ECT(0) | ECT(1) | CE |
81 * Inner Hdr: +---------+------------+------------+------------+
82 * Not-ECT | Not-ECT |Not-ECT(!!!)|Not-ECT(!!!)| <drop>(!!!)|
83 * ECT(0) | ECT(0) | ECT(0) | ECT(1) | CE |
84 * ECT(1) | ECT(1) | ECT(1) (!) | ECT(1) | CE |
85 * CE | CE | CE | CE(!!!)| CE |
86 * +---------+------------+------------+------------+
87 *
88 * ECN_COMPLETE (normal mode with security log):
89 * certain combinations indicated in table by '(!!!)' or '(!)',
90 * where '(!!!)' means the combination always potentially dangerous which
91 * returns 3, while '(!)' means possibly dangerous in which returns 2.
92 * These combinations are unsed by previous ECN tunneling specifications
93 * and could be logged. Also, in case of more dangerous ones, the
94 * decapsulator SHOULD log the event and MAY also raise an alarm.
95 *
96 * Note: Caller SHOULD use rate-limited alarms so that the anomalous
97 * combinations will not amplify into a flood of alarm messages.
98 * Also, it MUST be possible to suppress alarms or logging.
99 *
100 * ECN_FORBIDDEN (compatibility mode):
101 * (I) set the ECN field to not-ECT in the outer header.
102 *
103 * (E) if the ECN field in the outer header is set to CE, drop the packet.
104 * otherwise, make no change to the ECN field in the inner header.
105 *
106 * the drop rule is for backward compatibility and protection against
107 * erasure of CE.
108 */
109
110 /*
111 * modify outer ECN (TOS) field on ingress operation (tunnel encapsulation).
112 */
113 void
ip_ecn_ingress(int mode,uint8_t * outer,const uint8_t * inner)114 ip_ecn_ingress(int mode, uint8_t *outer, const uint8_t *inner)
115 {
116
117 KASSERT(outer != NULL && inner != NULL,
118 ("NULL pointer passed to %s", __func__));
119
120 *outer = *inner;
121 switch (mode) {
122 case ECN_COMPLETE:
123 case ECN_ALLOWED:
124 /* normal mode: always copy the ECN field. */
125 break;
126
127 case ECN_FORBIDDEN:
128 /* compatibility mode: set not-ECT to the outer */
129 *outer &= ~IPTOS_ECN_MASK;
130 break;
131
132 case ECN_NOCARE:
133 break;
134 }
135 }
136
137 /*
138 * modify inner ECN (TOS) field on egress operation (tunnel decapsulation).
139 * the caller should drop the packet if the return value is 0.
140 */
141 int
ip_ecn_egress(int mode,const uint8_t * outer,uint8_t * inner)142 ip_ecn_egress(int mode, const uint8_t *outer, uint8_t *inner)
143 {
144
145 KASSERT(outer != NULL && inner != NULL,
146 ("NULL pointer passed to %s", __func__));
147
148 switch (mode) {
149 case ECN_COMPLETE:
150 if ((*outer & IPTOS_ECN_MASK) == IPTOS_ECN_ECT0) {
151 /* if the outer is ECT(0) and inner is ECT(1) raise a warning */
152 if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_ECT1)
153 return (ECN_WARN);
154 /* if the inner is not-ECT and outer is ECT(0) raise an alarm */
155 if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_NOTECT)
156 return (ECN_ALARM);
157 return (ECN_SUCCESS);
158 } else if ((*outer & IPTOS_ECN_MASK) == IPTOS_ECN_ECT1) {
159 /* if the outer is ECT(1) and inner is CE or ECT(1), raise an alarm */
160 if (((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_CE) ||
161 ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_NOTECT))
162 return (ECN_ALARM);
163 /* if the outer is ECT(1) and inner is ECT(0), copy ECT(1) */
164 if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_ECT0)
165 *inner = IPTOS_ECN_ECT1;
166 return (ECN_SUCCESS);
167 }
168 /* fallthrough */
169 case ECN_ALLOWED:
170 if ((*outer & IPTOS_ECN_MASK) == IPTOS_ECN_CE) {
171 /* if the outer is CE and the inner is not-ECT, drop it. */
172 if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_NOTECT)
173 return (ECN_DROP);
174 /* otherwise, copy CE */
175 *inner |= IPTOS_ECN_CE;
176 } else if ((*outer & IPTOS_ECN_MASK) == IPTOS_ECN_ECT1) {
177 /* if the outer is ECT(1) and inner is ECT(0), copy ECT(1) */
178 if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_ECT0)
179 *inner = IPTOS_ECN_ECT1;
180 }
181 break;
182
183 case ECN_FORBIDDEN:
184 /*
185 * compatibility mode: if the outer is CE, should drop it.
186 * otherwise, leave the inner.
187 */
188 if ((*outer & IPTOS_ECN_MASK) == IPTOS_ECN_CE)
189 return (ECN_DROP);
190 break;
191
192 case ECN_NOCARE:
193 break;
194 }
195 return (ECN_SUCCESS);
196 }
197
198 #ifdef INET6
199 void
ip6_ecn_ingress(int mode,uint32_t * outer,const uint32_t * inner)200 ip6_ecn_ingress(int mode, uint32_t *outer, const uint32_t *inner)
201 {
202 uint8_t outer8, inner8;
203
204 KASSERT(outer != NULL && inner != NULL,
205 ("NULL pointer passed to %s", __func__));
206
207 inner8 = (ntohl(*inner) >> IPV6_FLOWLABEL_LEN) & 0xff;
208 ip_ecn_ingress(mode, &outer8, &inner8);
209 *outer &= ~htonl(0xff << IPV6_FLOWLABEL_LEN);
210 *outer |= htonl((uint32_t)outer8 << IPV6_FLOWLABEL_LEN);
211 }
212
213 int
ip6_ecn_egress(int mode,const uint32_t * outer,uint32_t * inner)214 ip6_ecn_egress(int mode, const uint32_t *outer, uint32_t *inner)
215 {
216 uint8_t outer8, inner8, oinner8;
217 int ret;
218
219 KASSERT(outer != NULL && inner != NULL,
220 ("NULL pointer passed to %s", __func__));
221
222 outer8 = (ntohl(*outer) >> IPV6_FLOWLABEL_LEN) & 0xff;
223 inner8 = oinner8 = (ntohl(*inner) >> IPV6_FLOWLABEL_LEN) & 0xff;
224
225 ret = ip_ecn_egress(mode, &outer8, &inner8);
226 if (ret == ECN_DROP)
227 return (ECN_DROP);
228 if (inner8 != oinner8) {
229 *inner &= ~htonl(0xff << IPV6_FLOWLABEL_LEN);
230 *inner |= htonl((uint32_t)inner8 << IPV6_FLOWLABEL_LEN);
231 }
232 return (ret);
233 }
234 #endif
235