xref: /openbsd/sys/netinet/ipsec_output.c (revision 940d25ac)
1 /*	$OpenBSD: ipsec_output.c,v 1.98 2024/02/11 01:27:45 bluhm Exp $ */
2 /*
3  * The author of this code is Angelos D. Keromytis (angelos@cis.upenn.edu)
4  *
5  * Copyright (c) 2000-2001 Angelos D. Keromytis.
6  *
7  * Permission to use, copy, and modify this software with or without fee
8  * is hereby granted, provided that this entire notice is included in
9  * all copies of any software which is or includes a copy or
10  * modification of this software.
11  * You may use this code under the GNU public license if you so wish. Please
12  * contribute changes back to the authors under this freer than GPL license
13  * so that we may further the use of strong encryption without limitations to
14  * all.
15  *
16  * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR
17  * IMPLIED WARRANTY. IN PARTICULAR, NONE OF THE AUTHORS MAKES ANY
18  * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE
19  * MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR
20  * PURPOSE.
21  */
22 
23 #include "pf.h"
24 
25 #include <sys/param.h>
26 #include <sys/systm.h>
27 #include <sys/mbuf.h>
28 #include <sys/socket.h>
29 #include <sys/kernel.h>
30 #include <sys/timeout.h>
31 
32 #include <net/if.h>
33 #include <net/route.h>
34 
35 #include <netinet/in.h>
36 #include <netinet/ip.h>
37 #include <netinet/in_pcb.h>
38 #include <netinet/ip_var.h>
39 #include <netinet6/ip6_var.h>
40 
41 #if NPF > 0
42 #include <net/pfvar.h>
43 #endif
44 
45 #include <netinet/udp.h>
46 #include <netinet/ip_ipip.h>
47 #include <netinet/ip_ah.h>
48 #include <netinet/ip_esp.h>
49 #include <netinet/ip_ipcomp.h>
50 
51 #include <crypto/cryptodev.h>
52 #include <crypto/xform.h>
53 
54 #ifdef ENCDEBUG
55 #define DPRINTF(fmt, args...)						\
56 	do {								\
57 		if (encdebug)						\
58 			printf("%s: " fmt "\n", __func__, ## args);	\
59 	} while (0)
60 #else
61 #define DPRINTF(fmt, args...)						\
62 	do { } while (0)
63 #endif
64 
65 int	udpencap_enable = 1;	/* enabled by default */
66 int	udpencap_port = 4500;	/* triggers decapsulation */
67 
68 /*
69  * Loop over a tdb chain, taking into consideration protocol tunneling. The
70  * fourth argument is set if the first encapsulation header is already in
71  * place.
72  */
73 int
ipsp_process_packet(struct mbuf * m,struct tdb * tdb,int af,int tunalready)74 ipsp_process_packet(struct mbuf *m, struct tdb *tdb, int af, int tunalready)
75 {
76 	int hlen, off, error;
77 #ifdef INET6
78 	struct ip6_ext ip6e;
79 	int nxt;
80 	int dstopt = 0;
81 #endif
82 
83 	int setdf = 0;
84 	struct ip *ip;
85 #ifdef INET6
86 	struct ip6_hdr *ip6;
87 #endif /* INET6 */
88 
89 #ifdef ENCDEBUG
90 	char buf[INET6_ADDRSTRLEN];
91 #endif
92 
93 	/* Check that the transform is allowed by the administrator. */
94 	if ((tdb->tdb_sproto == IPPROTO_ESP && !esp_enable) ||
95 	    (tdb->tdb_sproto == IPPROTO_AH && !ah_enable) ||
96 	    (tdb->tdb_sproto == IPPROTO_IPCOMP && !ipcomp_enable)) {
97 		DPRINTF("IPsec outbound packet dropped due to policy "
98 		    "(check your sysctls)");
99 		error = EHOSTUNREACH;
100 		goto drop;
101 	}
102 
103 	/* Sanity check. */
104 	if (!tdb->tdb_xform) {
105 		DPRINTF("uninitialized TDB");
106 		error = EHOSTUNREACH;
107 		goto drop;
108 	}
109 
110 	/* Check if the SPI is invalid. */
111 	if (tdb->tdb_flags & TDBF_INVALID) {
112 		DPRINTF("attempt to use invalid SA %s/%08x/%u",
113 		    ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
114 		    ntohl(tdb->tdb_spi), tdb->tdb_sproto);
115 		error = ENXIO;
116 		goto drop;
117 	}
118 
119 	/* Check that the network protocol is supported */
120 	switch (tdb->tdb_dst.sa.sa_family) {
121 	case AF_INET:
122 		break;
123 
124 #ifdef INET6
125 	case AF_INET6:
126 		break;
127 #endif /* INET6 */
128 
129 	default:
130 		DPRINTF("attempt to use SA %s/%08x/%u for protocol family %d",
131 		    ipsp_address(&tdb->tdb_dst, buf, sizeof(buf)),
132 		    ntohl(tdb->tdb_spi), tdb->tdb_sproto,
133 		    tdb->tdb_dst.sa.sa_family);
134 		error = EPFNOSUPPORT;
135 		goto drop;
136 	}
137 
138 	/*
139 	 * Register first use if applicable, setup relevant expiration timer.
140 	 */
141 	if (tdb->tdb_first_use == 0) {
142 		tdb->tdb_first_use = gettime();
143 		if (tdb->tdb_flags & TDBF_FIRSTUSE) {
144 			if (timeout_add_sec(&tdb->tdb_first_tmo,
145 			    tdb->tdb_exp_first_use))
146 				tdb_ref(tdb);
147 		}
148 		if (tdb->tdb_flags & TDBF_SOFT_FIRSTUSE) {
149 			if (timeout_add_sec(&tdb->tdb_sfirst_tmo,
150 			    tdb->tdb_soft_first_use))
151 				tdb_ref(tdb);
152 		}
153 	}
154 
155 	/*
156 	 * Check for tunneling if we don't have the first header in place.
157 	 * When doing Ethernet-over-IP, we are handed an already-encapsulated
158 	 * frame, so we don't need to re-encapsulate.
159 	 */
160 	if (tunalready == 0) {
161 		/*
162 		 * If the target protocol family is different, we know we'll be
163 		 * doing tunneling.
164 		 */
165 		if (af == tdb->tdb_dst.sa.sa_family) {
166 			switch (af) {
167 			case AF_INET:
168 				hlen = sizeof(struct ip);
169 				break;
170 #ifdef INET6
171 			case AF_INET6:
172 				hlen = sizeof(struct ip6_hdr);
173 				break;
174 #endif /* INET6 */
175 			}
176 
177 			/* Bring the network header in the first mbuf. */
178 			if (m->m_len < hlen) {
179 				if ((m = m_pullup(m, hlen)) == NULL) {
180 					error = ENOBUFS;
181 					goto drop;
182 				}
183 			}
184 
185 			if (af == AF_INET) {
186 				ip = mtod(m, struct ip *);
187 
188 				/*
189 				 * This is not a bridge packet, remember if we
190 				 * had IP_DF.
191 				 */
192 				setdf = ip->ip_off & htons(IP_DF);
193 			}
194 
195 #ifdef INET6
196 			if (af == AF_INET6)
197 				ip6 = mtod(m, struct ip6_hdr *);
198 #endif /* INET6 */
199 		}
200 
201 		/* Do the appropriate encapsulation, if necessary. */
202 		if ((tdb->tdb_dst.sa.sa_family != af) || /* PF mismatch */
203 		    (tdb->tdb_flags & TDBF_TUNNELING) || /* Tunneling needed */
204 		    (tdb->tdb_xform->xf_type == XF_IP4) || /* ditto */
205 		    ((tdb->tdb_dst.sa.sa_family == AF_INET) &&
206 		     (tdb->tdb_dst.sin.sin_addr.s_addr != INADDR_ANY) &&
207 		     (tdb->tdb_dst.sin.sin_addr.s_addr != ip->ip_dst.s_addr)) ||
208 #ifdef INET6
209 		    ((tdb->tdb_dst.sa.sa_family == AF_INET6) &&
210 		     (!IN6_IS_ADDR_UNSPECIFIED(&tdb->tdb_dst.sin6.sin6_addr)) &&
211 		     (!IN6_ARE_ADDR_EQUAL(&tdb->tdb_dst.sin6.sin6_addr,
212 		      &ip6->ip6_dst))) ||
213 #endif /* INET6 */
214 		    0) {
215 			/* Fix IPv4 header checksum and length. */
216 			if (af == AF_INET) {
217 				if (m->m_len < sizeof(struct ip))
218 					if ((m = m_pullup(m,
219 					    sizeof(struct ip))) == NULL) {
220 						error = ENOBUFS;
221 						goto drop;
222 					}
223 
224 				ip = mtod(m, struct ip *);
225 				ip->ip_len = htons(m->m_pkthdr.len);
226 				ip->ip_sum = 0;
227 				ip->ip_sum = in_cksum(m, ip->ip_hl << 2);
228 			}
229 
230 #ifdef INET6
231 			/* Fix IPv6 header payload length. */
232 			if (af == AF_INET6) {
233 				if (m->m_len < sizeof(struct ip6_hdr))
234 					if ((m = m_pullup(m,
235 					    sizeof(struct ip6_hdr))) == NULL) {
236 						error = ENOBUFS;
237 						goto drop;
238 					}
239 
240 				if (m->m_pkthdr.len - sizeof(*ip6) >
241 				    IPV6_MAXPACKET) {
242 					/* No jumbogram support. */
243 					error = ENXIO;	/*?*/
244 					goto drop;
245 				}
246 				ip6 = mtod(m, struct ip6_hdr *);
247 				ip6->ip6_plen = htons(m->m_pkthdr.len
248 				    - sizeof(*ip6));
249 			}
250 #endif /* INET6 */
251 
252 			/* Encapsulate -- m may be changed or set to NULL. */
253 			error = ipip_output(&m, tdb);
254 			if ((m == NULL) && (!error))
255 				error = EFAULT;
256 			if (error)
257 				goto drop;
258 
259 			if (tdb->tdb_dst.sa.sa_family == AF_INET && setdf) {
260 				if (m->m_len < sizeof(struct ip))
261 					if ((m = m_pullup(m,
262 					    sizeof(struct ip))) == NULL) {
263 						error = ENOBUFS;
264 						goto drop;
265 					}
266 
267 				ip = mtod(m, struct ip *);
268 				ip->ip_off |= htons(IP_DF);
269 			}
270 
271 			/* Remember that we appended a tunnel header. */
272 			mtx_enter(&tdb->tdb_mtx);
273 			tdb->tdb_flags |= TDBF_USEDTUNNEL;
274 			mtx_leave(&tdb->tdb_mtx);
275 		}
276 	}
277 
278 	/*
279 	 * If this is just an IP-IP TDB and we're told there's already an
280 	 * encapsulation header or ipip_output() has encapsulated it, move on.
281 	 */
282 	if (tdb->tdb_xform->xf_type == XF_IP4)
283 		return ipsp_process_done(m, tdb);
284 
285 	/* Extract some information off the headers. */
286 	switch (tdb->tdb_dst.sa.sa_family) {
287 	case AF_INET:
288 		ip = mtod(m, struct ip *);
289 		hlen = ip->ip_hl << 2;
290 		off = offsetof(struct ip, ip_p);
291 		break;
292 
293 #ifdef INET6
294 	case AF_INET6:
295 		ip6 = mtod(m, struct ip6_hdr *);
296 		hlen = sizeof(struct ip6_hdr);
297 		off = offsetof(struct ip6_hdr, ip6_nxt);
298 		nxt = ip6->ip6_nxt;
299 		/*
300 		 * chase mbuf chain to find the appropriate place to
301 		 * put AH/ESP/IPcomp header.
302 		 *	IPv6 hbh dest1 rthdr ah* [esp* dest2 payload]
303 		 */
304 		do {
305 			switch (nxt) {
306 			case IPPROTO_AH:
307 			case IPPROTO_ESP:
308 			case IPPROTO_IPCOMP:
309 				/*
310 				 * we should not skip security header added
311 				 * beforehand.
312 				 */
313 				goto exitip6loop;
314 
315 			case IPPROTO_HOPOPTS:
316 			case IPPROTO_DSTOPTS:
317 			case IPPROTO_ROUTING:
318 				/*
319 				 * if we see 2nd destination option header,
320 				 * we should stop there.
321 				 */
322 				if (nxt == IPPROTO_DSTOPTS && dstopt)
323 					goto exitip6loop;
324 
325 				if (nxt == IPPROTO_DSTOPTS) {
326 					/*
327 					 * seen 1st or 2nd destination option.
328 					 * next time we see one, it must be 2nd.
329 					 */
330 					dstopt = 1;
331 				} else if (nxt == IPPROTO_ROUTING) {
332 					/*
333 					 * if we see destination option next
334 					 * time, it must be dest2.
335 					 */
336 					dstopt = 2;
337 				}
338 				if (m->m_pkthdr.len < hlen + sizeof(ip6e)) {
339 					error = EINVAL;
340 					goto drop;
341 				}
342 				/* skip this header */
343 				m_copydata(m, hlen, sizeof(ip6e),
344 				    (caddr_t)&ip6e);
345 				nxt = ip6e.ip6e_nxt;
346 				off = hlen + offsetof(struct ip6_ext, ip6e_nxt);
347 				/*
348 				 * we will never see nxt == IPPROTO_AH
349 				 * so it is safe to omit AH case.
350 				 */
351 				hlen += (ip6e.ip6e_len + 1) << 3;
352 				break;
353 			default:
354 				goto exitip6loop;
355 			}
356 		} while (hlen < m->m_pkthdr.len);
357 	exitip6loop:
358 		break;
359 #endif /* INET6 */
360 	default:
361 		error = EPFNOSUPPORT;
362 		goto drop;
363 	}
364 
365 	if (m->m_pkthdr.len < hlen) {
366 		error = EINVAL;
367 		goto drop;
368 	}
369 
370 	ipsecstat_add(ipsec_ouncompbytes, m->m_pkthdr.len);
371 	tdbstat_add(tdb, tdb_ouncompbytes, m->m_pkthdr.len);
372 
373 	/* Non expansion policy for IPCOMP */
374 	if (tdb->tdb_sproto == IPPROTO_IPCOMP) {
375 		if ((m->m_pkthdr.len - hlen) < tdb->tdb_compalgxform->minlen) {
376 			/* No need to compress, leave the packet untouched */
377 			ipcompstat_inc(ipcomps_minlen);
378 			return ipsp_process_done(m, tdb);
379 		}
380 	}
381 
382 	/* Invoke the IPsec transform. */
383 	return (*(tdb->tdb_xform->xf_output))(m, tdb, hlen, off);
384 
385  drop:
386 	m_freem(m);
387 	return error;
388 }
389 
390 /*
391  * Called by the IPsec output transform callbacks, to transmit the packet
392  * or do further processing, as necessary.
393  */
394 int
ipsp_process_done(struct mbuf * m,struct tdb * tdb)395 ipsp_process_done(struct mbuf *m, struct tdb *tdb)
396 {
397 	struct ip *ip;
398 #ifdef INET6
399 	struct ip6_hdr *ip6;
400 #endif /* INET6 */
401 	struct tdb *tdbo;
402 	struct tdb_ident *tdbi;
403 	struct m_tag *mtag;
404 	int roff, error;
405 
406 	NET_ASSERT_LOCKED();
407 
408 	tdb->tdb_last_used = gettime();
409 
410 	if ((tdb->tdb_flags & TDBF_UDPENCAP) != 0) {
411 		struct mbuf *mi;
412 		struct udphdr *uh;
413 		int iphlen;
414 
415 		if (!udpencap_enable || !udpencap_port) {
416 			error = ENXIO;
417 			goto drop;
418 		}
419 
420 		switch (tdb->tdb_dst.sa.sa_family) {
421 		case AF_INET:
422 			iphlen = sizeof(struct ip);
423 			break;
424 #ifdef INET6
425 		case AF_INET6:
426 			iphlen = sizeof(struct ip6_hdr);
427 			break;
428 #endif /* INET6 */
429 		default:
430 			DPRINTF("unknown protocol family (%d)",
431 			    tdb->tdb_dst.sa.sa_family);
432 			error = EPFNOSUPPORT;
433 			goto drop;
434 		}
435 
436 		mi = m_makespace(m, iphlen, sizeof(struct udphdr), &roff);
437 		if (mi == NULL) {
438 			error = ENOMEM;
439 			goto drop;
440 		}
441 		uh = (struct udphdr *)(mtod(mi, caddr_t) + roff);
442 		uh->uh_sport = uh->uh_dport = htons(udpencap_port);
443 		if (tdb->tdb_udpencap_port)
444 			uh->uh_dport = tdb->tdb_udpencap_port;
445 
446 		uh->uh_ulen = htons(m->m_pkthdr.len - iphlen);
447 		uh->uh_sum = 0;
448 #ifdef INET6
449 		if (tdb->tdb_dst.sa.sa_family == AF_INET6)
450 			m->m_pkthdr.csum_flags |= M_UDP_CSUM_OUT;
451 #endif /* INET6 */
452 		espstat_inc(esps_udpencout);
453 	}
454 
455 	switch (tdb->tdb_dst.sa.sa_family) {
456 	case AF_INET:
457 		/* Fix the header length, for AH processing. */
458 		ip = mtod(m, struct ip *);
459 		ip->ip_len = htons(m->m_pkthdr.len);
460 		if ((tdb->tdb_flags & TDBF_UDPENCAP) != 0)
461 			ip->ip_p = IPPROTO_UDP;
462 		break;
463 
464 #ifdef INET6
465 	case AF_INET6:
466 		/* Fix the header length, for AH processing. */
467 		if (m->m_pkthdr.len < sizeof(*ip6)) {
468 			error = ENXIO;
469 			goto drop;
470 		}
471 		if (m->m_pkthdr.len - sizeof(*ip6) > IPV6_MAXPACKET) {
472 			/* No jumbogram support. */
473 			error = ENXIO;
474 			goto drop;
475 		}
476 		ip6 = mtod(m, struct ip6_hdr *);
477 		ip6->ip6_plen = htons(m->m_pkthdr.len - sizeof(*ip6));
478 		if ((tdb->tdb_flags & TDBF_UDPENCAP) != 0)
479 			ip6->ip6_nxt = IPPROTO_UDP;
480 		break;
481 #endif /* INET6 */
482 
483 	default:
484 		DPRINTF("unknown protocol family (%d)",
485 		    tdb->tdb_dst.sa.sa_family);
486 		error = EPFNOSUPPORT;
487 		goto drop;
488 	}
489 
490 	/*
491 	 * Add a record of what we've done or what needs to be done to the
492 	 * packet.
493 	 */
494 	mtag = m_tag_get(PACKET_TAG_IPSEC_OUT_DONE, sizeof(struct tdb_ident),
495 	    M_NOWAIT);
496 	if (mtag == NULL) {
497 		DPRINTF("could not allocate packet tag");
498 		error = ENOMEM;
499 		goto drop;
500 	}
501 
502 	tdbi = (struct tdb_ident *)(mtag + 1);
503 	tdbi->dst = tdb->tdb_dst;
504 	tdbi->proto = tdb->tdb_sproto;
505 	tdbi->spi = tdb->tdb_spi;
506 	tdbi->rdomain = tdb->tdb_rdomain;
507 
508 	m_tag_prepend(m, mtag);
509 
510 	ipsecstat_pkt(ipsec_opackets, ipsec_obytes, m->m_pkthdr.len);
511 	tdbstat_pkt(tdb, tdb_opackets, tdb_obytes, m->m_pkthdr.len);
512 
513 	/* If there's another (bundled) TDB to apply, do so. */
514 	tdbo = tdb_ref(tdb->tdb_onext);
515 	if (tdbo != NULL) {
516 		KERNEL_ASSERT_LOCKED();
517 		error = ipsp_process_packet(m, tdbo,
518 		    tdb->tdb_dst.sa.sa_family, 0);
519 		tdb_unref(tdbo);
520 		return error;
521 	}
522 
523 #if NPF > 0
524 	/* Add pf tag if requested. */
525 	pf_tag_packet(m, tdb->tdb_tag, -1);
526 	pf_pkt_addr_changed(m);
527 #endif
528 	if (tdb->tdb_rdomain != tdb->tdb_rdomain_post)
529 		m->m_pkthdr.ph_rtableid = tdb->tdb_rdomain_post;
530 
531 	/*
532 	 * We're done with IPsec processing, transmit the packet using the
533 	 * appropriate network protocol (IP or IPv6). SPD lookup will be
534 	 * performed again there.
535 	 */
536 	switch (tdb->tdb_dst.sa.sa_family) {
537 	case AF_INET:
538 		error = ip_output(m, NULL, NULL, IP_RAWOUTPUT, NULL, NULL, 0);
539 		break;
540 #ifdef INET6
541 	case AF_INET6:
542 		/*
543 		 * We don't need massage, IPv6 header fields are always in
544 		 * net endian.
545 		 */
546 		error = ip6_output(m, NULL, NULL, 0, NULL, NULL);
547 		break;
548 #endif /* INET6 */
549 	default:
550 		error = EPFNOSUPPORT;
551 		break;
552 	}
553 	return error;
554 
555  drop:
556 	m_freem(m);
557 	return error;
558 }
559 
560 ssize_t
ipsec_hdrsz(struct tdb * tdbp)561 ipsec_hdrsz(struct tdb *tdbp)
562 {
563 	ssize_t adjust;
564 
565 	switch (tdbp->tdb_sproto) {
566 	case IPPROTO_IPIP:
567 		adjust = 0;
568 		break;
569 
570 	case IPPROTO_ESP:
571 		if (tdbp->tdb_encalgxform == NULL)
572 			return (-1);
573 
574 		/* Header length */
575 		adjust = 2 * sizeof(u_int32_t) + tdbp->tdb_ivlen;
576 		if (tdbp->tdb_flags & TDBF_UDPENCAP)
577 			adjust += sizeof(struct udphdr);
578 		/* Authenticator */
579 		if (tdbp->tdb_authalgxform != NULL)
580 			adjust += tdbp->tdb_authalgxform->authsize;
581 		/* Padding */
582 		adjust += MAX(4, tdbp->tdb_encalgxform->blocksize);
583 		break;
584 
585 	case IPPROTO_AH:
586 		if (tdbp->tdb_authalgxform == NULL)
587 			return (-1);
588 
589 		adjust = AH_FLENGTH + sizeof(u_int32_t);
590 		adjust += tdbp->tdb_authalgxform->authsize;
591 		break;
592 
593 	default:
594 		return (-1);
595 	}
596 
597 	if (!(tdbp->tdb_flags & TDBF_TUNNELING) &&
598 	    !(tdbp->tdb_flags & TDBF_USEDTUNNEL))
599 		return (adjust);
600 
601 	switch (tdbp->tdb_dst.sa.sa_family) {
602 	case AF_INET:
603 		adjust += sizeof(struct ip);
604 		break;
605 #ifdef INET6
606 	case AF_INET6:
607 		adjust += sizeof(struct ip6_hdr);
608 		break;
609 #endif /* INET6 */
610 	}
611 
612 	return (adjust);
613 }
614 
615 void
ipsec_adjust_mtu(struct mbuf * m,u_int32_t mtu)616 ipsec_adjust_mtu(struct mbuf *m, u_int32_t mtu)
617 {
618 	struct tdb_ident *tdbi;
619 	struct tdb *tdbp;
620 	struct m_tag *mtag;
621 	ssize_t adjust;
622 
623 	NET_ASSERT_LOCKED();
624 
625 	for (mtag = m_tag_find(m, PACKET_TAG_IPSEC_OUT_DONE, NULL); mtag;
626 	     mtag = m_tag_find(m, PACKET_TAG_IPSEC_OUT_DONE, mtag)) {
627 		tdbi = (struct tdb_ident *)(mtag + 1);
628 		tdbp = gettdb(tdbi->rdomain, tdbi->spi, &tdbi->dst,
629 		    tdbi->proto);
630 		if (tdbp == NULL)
631 			break;
632 
633 		if ((adjust = ipsec_hdrsz(tdbp)) == -1) {
634 			tdb_unref(tdbp);
635 			break;
636 		}
637 
638 		mtu -= adjust;
639 		tdbp->tdb_mtu = mtu;
640 		tdbp->tdb_mtutimeout = gettime() + ip_mtudisc_timeout;
641 		DPRINTF("spi %08x mtu %d adjust %ld mbuf %p",
642 		    ntohl(tdbp->tdb_spi), tdbp->tdb_mtu, adjust, m);
643 		tdb_unref(tdbp);
644 	}
645 }
646