scope6.c revision 190787
1139826Simp/*- 262587Sitojun * Copyright (C) 2000 WIDE Project. 362587Sitojun * All rights reserved. 4120941Sume * 562587Sitojun * Redistribution and use in source and binary forms, with or without 662587Sitojun * modification, are permitted provided that the following conditions 762587Sitojun * are met: 862587Sitojun * 1. Redistributions of source code must retain the above copyright 962587Sitojun * notice, this list of conditions and the following disclaimer. 1062587Sitojun * 2. Redistributions in binary form must reproduce the above copyright 1162587Sitojun * notice, this list of conditions and the following disclaimer in the 1262587Sitojun * documentation and/or other materials provided with the distribution. 1362587Sitojun * 3. Neither the name of the project nor the names of its contributors 1462587Sitojun * may be used to endorse or promote products derived from this software 1562587Sitojun * without specific prior written permission. 16120941Sume * 1762587Sitojun * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND 1862587Sitojun * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1962587Sitojun * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2062587Sitojun * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE 2162587Sitojun * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2262587Sitojun * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2362587Sitojun * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 2462587Sitojun * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 2562587Sitojun * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 2662587Sitojun * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2762587Sitojun * SUCH DAMAGE. 28174510Sobrien * 29174510Sobrien * $KAME: scope6.c,v 1.10 2000/07/24 13:29:31 itojun Exp $ 3062587Sitojun */ 3162587Sitojun 32174510Sobrien#include <sys/cdefs.h> 33174510Sobrien__FBSDID("$FreeBSD: head/sys/netinet6/scope6.c 190787 2009-04-06 22:29:41Z zec $"); 34174510Sobrien 35189106Sbz#include "opt_route.h" 36189106Sbz 3762587Sitojun#include <sys/param.h> 3862587Sitojun#include <sys/malloc.h> 3962587Sitojun#include <sys/mbuf.h> 4062587Sitojun#include <sys/socket.h> 4162587Sitojun#include <sys/systm.h> 4278064Sume#include <sys/queue.h> 43148385Sume#include <sys/syslog.h> 44181803Sbz#include <sys/vimage.h> 4562587Sitojun 4662587Sitojun#include <net/route.h> 4762587Sitojun#include <net/if.h> 48185571Sbz#include <net/vnet.h> 4962587Sitojun 5062587Sitojun#include <netinet/in.h> 51185571Sbz 52183550Szec#include <netinet/ip6.h> 5362587Sitojun#include <netinet6/in6_var.h> 5462587Sitojun#include <netinet6/scope6_var.h> 55185571Sbz#include <netinet6/vinet6.h> 5662587Sitojun 57148385Sume 58121343Sume/* 59138184Sgnn * The scope6_lock protects the global sid default stored in 60138184Sgnn * sid_default below. 61121343Sume */ 62121343Sumestatic struct mtx scope6_lock; 63121343Sume#define SCOPE6_LOCK_INIT() mtx_init(&scope6_lock, "scope6_lock", NULL, MTX_DEF) 64121343Sume#define SCOPE6_LOCK() mtx_lock(&scope6_lock) 65121343Sume#define SCOPE6_UNLOCK() mtx_unlock(&scope6_lock) 66121343Sume#define SCOPE6_LOCK_ASSERT() mtx_assert(&scope6_lock, MA_OWNED) 67121343Sume 68185088Szec#ifdef VIMAGE_GLOBALS 69121161Sumestatic struct scope6_id sid_default; 70185088Szecint ip6_use_defzone; 71185088Szec#endif 72185088Szec 73121161Sume#define SID(ifp) \ 74121161Sume (((struct in6_ifextra *)(ifp)->if_afdata[AF_INET6])->scope6_id) 7562587Sitojun 7662587Sitojunvoid 77171259Sdelphijscope6_init(void) 78121161Sume{ 79183550Szec INIT_VNET_INET6(curvnet); 80121161Sume 81185088Szec#ifdef ENABLE_DEFAULT_SCOPE 82185088Szec V_ip6_use_defzone = 1; 83185088Szec#else 84185088Szec V_ip6_use_defzone = 0; 85185088Szec#endif 86190787Szec bzero(&V_sid_default, sizeof(V_sid_default)); 87190787Szec 88190787Szec if (!IS_DEFAULT_VNET(curvnet)) 89190787Szec return; 90190787Szec 91121343Sume SCOPE6_LOCK_INIT(); 92121161Sume} 93121161Sume 94121161Sumestruct scope6_id * 95171259Sdelphijscope6_ifattach(struct ifnet *ifp) 9662587Sitojun{ 97121161Sume struct scope6_id *sid; 9862587Sitojun 99121161Sume sid = (struct scope6_id *)malloc(sizeof(*sid), M_IFADDR, M_WAITOK); 100121161Sume bzero(sid, sizeof(*sid)); 10162587Sitojun 10262587Sitojun /* 10362587Sitojun * XXX: IPV6_ADDR_SCOPE_xxx macros are not standard. 10462587Sitojun * Should we rather hardcode here? 10562587Sitojun */ 106121315Sume sid->s6id_list[IPV6_ADDR_SCOPE_INTFACELOCAL] = ifp->if_index; 107121161Sume sid->s6id_list[IPV6_ADDR_SCOPE_LINKLOCAL] = ifp->if_index; 10862587Sitojun#ifdef MULTI_SCOPE 10962587Sitojun /* by default, we don't care about scope boundary for these scopes. */ 110121161Sume sid->s6id_list[IPV6_ADDR_SCOPE_SITELOCAL] = 1; 111121161Sume sid->s6id_list[IPV6_ADDR_SCOPE_ORGLOCAL] = 1; 11262587Sitojun#endif 11362587Sitojun 114121161Sume return sid; 11562587Sitojun} 11662587Sitojun 117121161Sumevoid 118171259Sdelphijscope6_ifdetach(struct scope6_id *sid) 119121161Sume{ 120121161Sume 121121161Sume free(sid, M_IFADDR); 122121161Sume} 123121161Sume 12462587Sitojunint 125171259Sdelphijscope6_set(struct ifnet *ifp, struct scope6_id *idlist) 12662587Sitojun{ 127183550Szec INIT_VNET_NET(ifp->if_vnet); 128138184Sgnn int i; 12962587Sitojun int error = 0; 130138184Sgnn struct scope6_id *sid = NULL; 13162587Sitojun 132138184Sgnn IF_AFDATA_LOCK(ifp); 133138184Sgnn sid = SID(ifp); 134138184Sgnn 135138184Sgnn if (!sid) { /* paranoid? */ 136138184Sgnn IF_AFDATA_UNLOCK(ifp); 137120856Sume return (EINVAL); 138138184Sgnn } 13962587Sitojun 14062587Sitojun /* 14162587Sitojun * XXX: We need more consistency checks of the relationship among 14262587Sitojun * scopes (e.g. an organization should be larger than a site). 14362587Sitojun */ 14462587Sitojun 14562587Sitojun /* 14662587Sitojun * TODO(XXX): after setting, we should reflect the changes to 147120941Sume * interface addresses, routing table entries, PCB entries... 14862587Sitojun */ 14962587Sitojun 150121343Sume SCOPE6_LOCK(); 15162587Sitojun for (i = 0; i < 16; i++) { 152121161Sume if (idlist->s6id_list[i] && 153121161Sume idlist->s6id_list[i] != sid->s6id_list[i]) { 154121315Sume /* 155121315Sume * An interface zone ID must be the corresponding 156121315Sume * interface index by definition. 157121315Sume */ 158121315Sume if (i == IPV6_ADDR_SCOPE_INTFACELOCAL && 159121315Sume idlist->s6id_list[i] != ifp->if_index) { 160138184Sgnn IF_AFDATA_UNLOCK(ifp); 161138184Sgnn SCOPE6_UNLOCK(); 162121315Sume return (EINVAL); 163121315Sume } 164121315Sume 16562587Sitojun if (i == IPV6_ADDR_SCOPE_LINKLOCAL && 166181803Sbz idlist->s6id_list[i] > V_if_index) { 16762587Sitojun /* 16862587Sitojun * XXX: theoretically, there should be no 16962587Sitojun * relationship between link IDs and interface 17062587Sitojun * IDs, but we check the consistency for 17162587Sitojun * safety in later use. 17262587Sitojun */ 173138184Sgnn IF_AFDATA_UNLOCK(ifp); 174138184Sgnn SCOPE6_UNLOCK(); 175120856Sume return (EINVAL); 17662587Sitojun } 17762587Sitojun 17862587Sitojun /* 17962587Sitojun * XXX: we must need lots of work in this case, 18062587Sitojun * but we simply set the new value in this initial 18162587Sitojun * implementation. 18262587Sitojun */ 183121161Sume sid->s6id_list[i] = idlist->s6id_list[i]; 18462587Sitojun } 18562587Sitojun } 186121343Sume SCOPE6_UNLOCK(); 187138184Sgnn IF_AFDATA_UNLOCK(ifp); 18862587Sitojun 189120856Sume return (error); 19062587Sitojun} 19162587Sitojun 19262587Sitojunint 193171259Sdelphijscope6_get(struct ifnet *ifp, struct scope6_id *idlist) 19462587Sitojun{ 195138184Sgnn /* We only need to lock the interface's afdata for SID() to work. */ 196138184Sgnn IF_AFDATA_LOCK(ifp); 197121161Sume struct scope6_id *sid = SID(ifp); 198121161Sume 199138184Sgnn if (sid == NULL) { /* paranoid? */ 200138184Sgnn IF_AFDATA_UNLOCK(ifp); 201120856Sume return (EINVAL); 202138184Sgnn } 20362587Sitojun 204121343Sume SCOPE6_LOCK(); 205121161Sume *idlist = *sid; 206121343Sume SCOPE6_UNLOCK(); 20762587Sitojun 208138184Sgnn IF_AFDATA_UNLOCK(ifp); 209120856Sume return (0); 21062587Sitojun} 21162587Sitojun 21262587Sitojun 21362587Sitojun/* 21462587Sitojun * Get a scope of the address. Node-local, link-local, site-local or global. 21562587Sitojun */ 21662587Sitojunint 217171259Sdelphijin6_addrscope(struct in6_addr *addr) 21862587Sitojun{ 21962587Sitojun int scope; 22062587Sitojun 221121315Sume if (addr->s6_addr[0] == 0xfe) { 222121315Sume scope = addr->s6_addr[1] & 0xc0; 22362587Sitojun 22462587Sitojun switch (scope) { 22562587Sitojun case 0x80: 22662587Sitojun return IPV6_ADDR_SCOPE_LINKLOCAL; 22762587Sitojun break; 22862587Sitojun case 0xc0: 22962587Sitojun return IPV6_ADDR_SCOPE_SITELOCAL; 23062587Sitojun break; 23162587Sitojun default: 23262587Sitojun return IPV6_ADDR_SCOPE_GLOBAL; /* just in case */ 23362587Sitojun break; 23462587Sitojun } 23562587Sitojun } 23662587Sitojun 23762587Sitojun 238121315Sume if (addr->s6_addr[0] == 0xff) { 239121315Sume scope = addr->s6_addr[1] & 0x0f; 24062587Sitojun 24162587Sitojun /* 24262587Sitojun * due to other scope such as reserved, 24362587Sitojun * return scope doesn't work. 24462587Sitojun */ 24562587Sitojun switch (scope) { 246121315Sume case IPV6_ADDR_SCOPE_INTFACELOCAL: 247121315Sume return IPV6_ADDR_SCOPE_INTFACELOCAL; 24862587Sitojun break; 24962587Sitojun case IPV6_ADDR_SCOPE_LINKLOCAL: 25062587Sitojun return IPV6_ADDR_SCOPE_LINKLOCAL; 25162587Sitojun break; 25262587Sitojun case IPV6_ADDR_SCOPE_SITELOCAL: 25362587Sitojun return IPV6_ADDR_SCOPE_SITELOCAL; 25462587Sitojun break; 25562587Sitojun default: 25662587Sitojun return IPV6_ADDR_SCOPE_GLOBAL; 25762587Sitojun break; 25862587Sitojun } 25962587Sitojun } 26062587Sitojun 261121315Sume /* 262121315Sume * Regard loopback and unspecified addresses as global, since 263121315Sume * they have no ambiguity. 264121315Sume */ 26593128Sume if (bcmp(&in6addr_loopback, addr, sizeof(*addr) - 1) == 0) { 266121315Sume if (addr->s6_addr[15] == 1) /* loopback */ 26762587Sitojun return IPV6_ADDR_SCOPE_LINKLOCAL; 268121315Sume if (addr->s6_addr[15] == 0) /* unspecified */ 269121315Sume return IPV6_ADDR_SCOPE_GLOBAL; /* XXX: correct? */ 27062587Sitojun } 27162587Sitojun 27262587Sitojun return IPV6_ADDR_SCOPE_GLOBAL; 27362587Sitojun} 27462587Sitojun 275171259Sdelphij/* 276171259Sdelphij * ifp - note that this might be NULL 277171259Sdelphij */ 278171259Sdelphij 27962587Sitojunvoid 280171259Sdelphijscope6_setdefault(struct ifnet *ifp) 28162587Sitojun{ 282183550Szec INIT_VNET_INET6(ifp->if_vnet); 283183550Szec 28462587Sitojun /* 285138184Sgnn * Currently, this function just sets the default "interfaces" 286121161Sume * and "links" according to the given interface. 28762587Sitojun * We might eventually have to separate the notion of "link" from 28862587Sitojun * "interface" and provide a user interface to set the default. 28962587Sitojun */ 290121343Sume SCOPE6_LOCK(); 29162587Sitojun if (ifp) { 292181803Sbz V_sid_default.s6id_list[IPV6_ADDR_SCOPE_INTFACELOCAL] = 29362587Sitojun ifp->if_index; 294181803Sbz V_sid_default.s6id_list[IPV6_ADDR_SCOPE_LINKLOCAL] = 295121161Sume ifp->if_index; 296120941Sume } else { 297181803Sbz V_sid_default.s6id_list[IPV6_ADDR_SCOPE_INTFACELOCAL] = 0; 298181803Sbz V_sid_default.s6id_list[IPV6_ADDR_SCOPE_LINKLOCAL] = 0; 29962587Sitojun } 300121343Sume SCOPE6_UNLOCK(); 30162587Sitojun} 30262587Sitojun 30362587Sitojunint 304171259Sdelphijscope6_get_default(struct scope6_id *idlist) 30562587Sitojun{ 306183550Szec INIT_VNET_INET6(curvnet); 307121343Sume 308121343Sume SCOPE6_LOCK(); 309181803Sbz *idlist = V_sid_default; 310121343Sume SCOPE6_UNLOCK(); 31162587Sitojun 312120856Sume return (0); 31362587Sitojun} 31462587Sitojun 31562587Sitojunu_int32_t 316171259Sdelphijscope6_addr2default(struct in6_addr *addr) 31762587Sitojun{ 318183550Szec INIT_VNET_INET6(curvnet); 319121343Sume u_int32_t id; 320121343Sume 321121161Sume /* 322121161Sume * special case: The loopback address should be considered as 323121161Sume * link-local, but there's no ambiguity in the syntax. 324121161Sume */ 325121161Sume if (IN6_IS_ADDR_LOOPBACK(addr)) 326121161Sume return (0); 327121161Sume 328121343Sume /* 329121343Sume * XXX: 32-bit read is atomic on all our platforms, is it OK 330121343Sume * not to lock here? 331121343Sume */ 332121343Sume SCOPE6_LOCK(); 333181803Sbz id = V_sid_default.s6id_list[in6_addrscope(addr)]; 334121343Sume SCOPE6_UNLOCK(); 335121343Sume return (id); 33662587Sitojun} 337148385Sume 338148385Sume/* 339148385Sume * Validate the specified scope zone ID in the sin6_scope_id field. If the ID 340148385Sume * is unspecified (=0), needs to be specified, and the default zone ID can be 341148385Sume * used, the default value will be used. 342148385Sume * This routine then generates the kernel-internal form: if the address scope 343148385Sume * of is interface-local or link-local, embed the interface index in the 344148385Sume * address. 345148385Sume */ 346148385Sumeint 347171259Sdelphijsa6_embedscope(struct sockaddr_in6 *sin6, int defaultok) 348148385Sume{ 349183550Szec INIT_VNET_NET(curvnet); 350148385Sume struct ifnet *ifp; 351148385Sume u_int32_t zoneid; 352148385Sume 353148385Sume if ((zoneid = sin6->sin6_scope_id) == 0 && defaultok) 354148385Sume zoneid = scope6_addr2default(&sin6->sin6_addr); 355148385Sume 356148385Sume if (zoneid != 0 && 357148385Sume (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr) || 358148385Sume IN6_IS_ADDR_MC_INTFACELOCAL(&sin6->sin6_addr))) { 359148385Sume /* 360148385Sume * At this moment, we only check interface-local and 361148385Sume * link-local scope IDs, and use interface indices as the 362148385Sume * zone IDs assuming a one-to-one mapping between interfaces 363148385Sume * and links. 364148385Sume */ 365181803Sbz if (V_if_index < zoneid) 366148385Sume return (ENXIO); 367148385Sume ifp = ifnet_byindex(zoneid); 368148385Sume if (ifp == NULL) /* XXX: this can happen for some OS */ 369148385Sume return (ENXIO); 370148385Sume 371148385Sume /* XXX assignment to 16bit from 32bit variable */ 372148385Sume sin6->sin6_addr.s6_addr16[1] = htons(zoneid & 0xffff); 373148385Sume 374148385Sume sin6->sin6_scope_id = 0; 375148385Sume } 376148385Sume 377148385Sume return 0; 378148385Sume} 379148385Sume 380148385Sume/* 381148385Sume * generate standard sockaddr_in6 from embedded form. 382148385Sume */ 383148385Sumeint 384171259Sdelphijsa6_recoverscope(struct sockaddr_in6 *sin6) 385148385Sume{ 386183550Szec INIT_VNET_NET(curvnet); 387165118Sbz char ip6buf[INET6_ADDRSTRLEN]; 388148385Sume u_int32_t zoneid; 389148385Sume 390148385Sume if (sin6->sin6_scope_id != 0) { 391148385Sume log(LOG_NOTICE, 392148385Sume "sa6_recoverscope: assumption failure (non 0 ID): %s%%%d\n", 393165118Sbz ip6_sprintf(ip6buf, &sin6->sin6_addr), sin6->sin6_scope_id); 394148385Sume /* XXX: proceed anyway... */ 395148385Sume } 396148385Sume if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr) || 397148385Sume IN6_IS_ADDR_MC_INTFACELOCAL(&sin6->sin6_addr)) { 398148385Sume /* 399148385Sume * KAME assumption: link id == interface id 400148385Sume */ 401148385Sume zoneid = ntohs(sin6->sin6_addr.s6_addr16[1]); 402148385Sume if (zoneid) { 403148385Sume /* sanity check */ 404181803Sbz if (zoneid < 0 || V_if_index < zoneid) 405148385Sume return (ENXIO); 406148385Sume if (!ifnet_byindex(zoneid)) 407148385Sume return (ENXIO); 408148385Sume sin6->sin6_addr.s6_addr16[1] = 0; 409148385Sume sin6->sin6_scope_id = zoneid; 410148385Sume } 411148385Sume } 412148385Sume 413148385Sume return 0; 414148385Sume} 415148385Sume 416148385Sume/* 417148385Sume * Determine the appropriate scope zone ID for in6 and ifp. If ret_id is 418148385Sume * non NULL, it is set to the zone ID. If the zone ID needs to be embedded 419148385Sume * in the in6_addr structure, in6 will be modified. 420171259Sdelphij * 421171259Sdelphij * ret_id - unnecessary? 422148385Sume */ 423148385Sumeint 424171259Sdelphijin6_setscope(struct in6_addr *in6, struct ifnet *ifp, u_int32_t *ret_id) 425148385Sume{ 426148385Sume int scope; 427148385Sume u_int32_t zoneid = 0; 428148396Sume struct scope6_id *sid; 429148385Sume 430148396Sume IF_AFDATA_LOCK(ifp); 431148396Sume 432148396Sume sid = SID(ifp); 433148396Sume 434148385Sume#ifdef DIAGNOSTIC 435148385Sume if (sid == NULL) { /* should not happen */ 436148385Sume panic("in6_setscope: scope array is NULL"); 437148385Sume /* NOTREACHED */ 438148385Sume } 439148385Sume#endif 440148385Sume 441148385Sume /* 442148385Sume * special case: the loopback address can only belong to a loopback 443148385Sume * interface. 444148385Sume */ 445148385Sume if (IN6_IS_ADDR_LOOPBACK(in6)) { 446148399Sume if (!(ifp->if_flags & IFF_LOOPBACK)) { 447148396Sume IF_AFDATA_UNLOCK(ifp); 448148385Sume return (EINVAL); 449148399Sume } else { 450148385Sume if (ret_id != NULL) 451148385Sume *ret_id = 0; /* there's no ambiguity */ 452148396Sume IF_AFDATA_UNLOCK(ifp); 453148385Sume return (0); 454148385Sume } 455148385Sume } 456148385Sume 457148385Sume scope = in6_addrscope(in6); 458148385Sume 459148396Sume SCOPE6_LOCK(); 460148385Sume switch (scope) { 461148385Sume case IPV6_ADDR_SCOPE_INTFACELOCAL: /* should be interface index */ 462148385Sume zoneid = sid->s6id_list[IPV6_ADDR_SCOPE_INTFACELOCAL]; 463148385Sume break; 464148385Sume 465148385Sume case IPV6_ADDR_SCOPE_LINKLOCAL: 466148385Sume zoneid = sid->s6id_list[IPV6_ADDR_SCOPE_LINKLOCAL]; 467148385Sume break; 468148385Sume 469148385Sume case IPV6_ADDR_SCOPE_SITELOCAL: 470148385Sume zoneid = sid->s6id_list[IPV6_ADDR_SCOPE_SITELOCAL]; 471148385Sume break; 472148385Sume 473148385Sume case IPV6_ADDR_SCOPE_ORGLOCAL: 474148385Sume zoneid = sid->s6id_list[IPV6_ADDR_SCOPE_ORGLOCAL]; 475148385Sume break; 476148385Sume 477148385Sume default: 478148385Sume zoneid = 0; /* XXX: treat as global. */ 479148385Sume break; 480148385Sume } 481148396Sume SCOPE6_UNLOCK(); 482148396Sume IF_AFDATA_UNLOCK(ifp); 483148385Sume 484148385Sume if (ret_id != NULL) 485148385Sume *ret_id = zoneid; 486148385Sume 487148385Sume if (IN6_IS_SCOPE_LINKLOCAL(in6) || IN6_IS_ADDR_MC_INTFACELOCAL(in6)) 488148385Sume in6->s6_addr16[1] = htons(zoneid & 0xffff); /* XXX */ 489148385Sume 490148385Sume return (0); 491148385Sume} 492148385Sume 493148385Sume/* 494148385Sume * Just clear the embedded scope identifier. Return 0 if the original address 495148385Sume * is intact; return non 0 if the address is modified. 496148385Sume */ 497148385Sumeint 498171259Sdelphijin6_clearscope(struct in6_addr *in6) 499148385Sume{ 500148385Sume int modified = 0; 501148385Sume 502148385Sume if (IN6_IS_SCOPE_LINKLOCAL(in6) || IN6_IS_ADDR_MC_INTFACELOCAL(in6)) { 503148385Sume if (in6->s6_addr16[1] != 0) 504148385Sume modified = 1; 505148385Sume in6->s6_addr16[1] = 0; 506148385Sume } 507148385Sume 508148385Sume return (modified); 509148385Sume} 510