From 1df40b609ad5a622904eb652109c287fe9c93ec5 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 4 Dec 2008 14:19:53 -0500 Subject: NLM: Remove address eye-catcher buffers from nlm_host The h_name field in struct nlm_host is a just copy of h_nsmhandle->sm_name. Likewise, the contents of the h_addrbuf field should be identical to the sm_addrbuf field. The h_srcaddrbuf field is used only in one place for debugging. We can live without this until we get %pI formatting for printk(). Currently these buffers are 48 bytes, but we need to support scope IDs in IPv6 presentation addresses, which means making the buffers even larger. Instead, let's find ways to eliminate them to save space. Signed-off-by: Chuck Lever Signed-off-by: J. Bruce Fields --- fs/lockd/host.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'fs/lockd/host.c') diff --git a/fs/lockd/host.c b/fs/lockd/host.c index abdebf76b82..33bf67af7ab 100644 --- a/fs/lockd/host.c +++ b/fs/lockd/host.c @@ -206,6 +206,7 @@ static struct nlm_host *nlm_lookup_host(struct nlm_lookup_host_info *ni) goto out; } host->h_name = nsm->sm_name; + host->h_addrbuf = nsm->sm_addrbuf; memcpy(nlm_addr(host), ni->sap, ni->salen); host->h_addrlen = ni->salen; nlm_clear_port(nlm_addr(host)); @@ -232,11 +233,6 @@ static struct nlm_host *nlm_lookup_host(struct nlm_lookup_host_info *ni) nrhosts++; - nlm_display_address((struct sockaddr *)&host->h_addr, - host->h_addrbuf, sizeof(host->h_addrbuf)); - nlm_display_address((struct sockaddr *)&host->h_srcaddr, - host->h_srcaddrbuf, sizeof(host->h_srcaddrbuf)); - dprintk("lockd: nlm_lookup_host created host %s\n", host->h_name); @@ -378,8 +374,8 @@ nlm_bind_host(struct nlm_host *host) { struct rpc_clnt *clnt; - dprintk("lockd: nlm_bind_host %s (%s), my addr=%s\n", - host->h_name, host->h_addrbuf, host->h_srcaddrbuf); + dprintk("lockd: nlm_bind_host %s (%s)\n", + host->h_name, host->h_addrbuf); /* Lock host handle */ mutex_lock(&host->h_mutex); -- cgit v1.2.3 From 6999fb4016b2604c2f8a65586bba4a62a4b24ce7 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 4 Dec 2008 14:20:01 -0500 Subject: NLM: Remove AF_UNSPEC arm in nlm_display_address() AF_UNSPEC support is no longer needed in nlm_display_address() now that a presentation address is no longer generated for the h_srcaddr field. Signed-off-by: Chuck Lever Signed-off-by: J. Bruce Fields --- fs/lockd/host.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'fs/lockd/host.c') diff --git a/fs/lockd/host.c b/fs/lockd/host.c index 33bf67af7ab..beb5da81016 100644 --- a/fs/lockd/host.c +++ b/fs/lockd/host.c @@ -112,9 +112,6 @@ static void nlm_display_address(const struct sockaddr *sap, const struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap; switch (sap->sa_family) { - case AF_UNSPEC: - snprintf(buf, len, "unspecified"); - break; case AF_INET: snprintf(buf, len, "%pI4", &sin->sin_addr.s_addr); break; -- cgit v1.2.3 From bc995801a09d1fead0bec1356bfd836911c8eed7 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 4 Dec 2008 14:20:08 -0500 Subject: NLM: Support IPv6 scope IDs in nlm_display_address() Scope ID support is needed since the kernel's NSM implementation is about to use these displayed addresses as a mon_name in some cases. When nsm_use_hostnames is zero, without scope ID support NSM will fail to handle peers that contact us via a link-local address. Link-local addresses do not work without an interface ID, which is stored in the sockaddr's sin6_scope_id field. Signed-off-by: Chuck Lever Signed-off-by: J. Bruce Fields --- fs/lockd/host.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'fs/lockd/host.c') diff --git a/fs/lockd/host.c b/fs/lockd/host.c index beb5da81016..012e49aaecd 100644 --- a/fs/lockd/host.c +++ b/fs/lockd/host.c @@ -105,22 +105,31 @@ static void nlm_clear_port(struct sockaddr *sap) } } +static void nlm_display_ipv6_address(const struct sockaddr *sap, char *buf, + const size_t len) +{ + const struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap; + + if (ipv6_addr_v4mapped(&sin6->sin6_addr)) + snprintf(buf, len, "%pI4", &sin6->sin6_addr.s6_addr32[3]); + else if (sin6->sin6_scope_id != 0) + snprintf(buf, len, "%pI6%%%u", &sin6->sin6_addr, + sin6->sin6_scope_id); + else + snprintf(buf, len, "%pI6", &sin6->sin6_addr); +} + static void nlm_display_address(const struct sockaddr *sap, char *buf, const size_t len) { const struct sockaddr_in *sin = (struct sockaddr_in *)sap; - const struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap; switch (sap->sa_family) { case AF_INET: snprintf(buf, len, "%pI4", &sin->sin_addr.s_addr); break; case AF_INET6: - if (ipv6_addr_v4mapped(&sin6->sin6_addr)) - snprintf(buf, len, "%pI4", - &sin6->sin6_addr.s6_addr32[3]); - else - snprintf(buf, len, "%pI6", &sin6->sin6_addr); + nlm_display_ipv6_address(sap, buf, len); break; default: snprintf(buf, len, "unsupported address family"); -- cgit v1.2.3 From afb03699dc0a920aed3322ad0e6895533941fb1e Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 4 Dec 2008 14:20:16 -0500 Subject: NLM: Add helper to handle IPv4 addresses Clean up: introduce a helper function to generate IPv4 addresses using the same style as the IPv6 helper function we just added. Signed-off-by: Chuck Lever Signed-off-by: J. Bruce Fields --- fs/lockd/host.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'fs/lockd/host.c') diff --git a/fs/lockd/host.c b/fs/lockd/host.c index 012e49aaecd..780918acd6f 100644 --- a/fs/lockd/host.c +++ b/fs/lockd/host.c @@ -105,6 +105,13 @@ static void nlm_clear_port(struct sockaddr *sap) } } +static void nlm_display_ipv4_address(const struct sockaddr *sap, char *buf, + const size_t len) +{ + const struct sockaddr_in *sin = (struct sockaddr_in *)sap; + snprintf(buf, len, "%pI4", &sin->sin_addr.s_addr); +} + static void nlm_display_ipv6_address(const struct sockaddr *sap, char *buf, const size_t len) { @@ -122,11 +129,9 @@ static void nlm_display_ipv6_address(const struct sockaddr *sap, char *buf, static void nlm_display_address(const struct sockaddr *sap, char *buf, const size_t len) { - const struct sockaddr_in *sin = (struct sockaddr_in *)sap; - switch (sap->sa_family) { case AF_INET: - snprintf(buf, len, "%pI4", &sin->sin_addr.s_addr); + nlm_display_ipv4_address(sap, buf, len); break; case AF_INET6: nlm_display_ipv6_address(sap, buf, len); -- cgit v1.2.3 From c8c23c423dec49cb439697d3dc714e1500ff1610 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 4 Dec 2008 14:21:31 -0500 Subject: NSM: Release nsmhandle in nlm_destroy_host The nsm_handle's reference count is bumped in nlm_lookup_host(). It should be decremented in nlm_destroy_host() to make it easier to see the balance of these two operations. Move the nsm_release() call to fs/lockd/host.c. The h_nsmhandle pointer is set in nlm_lookup_host(), and never cleared. The nlm_destroy_host() function is never called for the same nlm_host twice, so h_nsmhandle won't ever be NULL when nsm_unmonitor() is called. All references to the nlm_host are gone before it is freed. We can skip making h_nsmhandle NULL just before the nlm_host is deallocated. It's also likely we can remove the h_nsmhandle NULL check in nlmsvc_is_client() as well, but we can do that later when rearchitect- ing the nlm_host cache. Signed-off-by: Chuck Lever Signed-off-by: J. Bruce Fields --- fs/lockd/host.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'fs/lockd/host.c') diff --git a/fs/lockd/host.c b/fs/lockd/host.c index 780918acd6f..1d523c1a7b6 100644 --- a/fs/lockd/host.c +++ b/fs/lockd/host.c @@ -37,6 +37,7 @@ static struct nsm_handle *nsm_find(const struct sockaddr *sap, const char *hostname, const size_t hostname_len, const int create); +static void nsm_release(struct nsm_handle *nsm); struct nlm_lookup_host_info { const int server; /* search for server|client */ @@ -263,10 +264,8 @@ nlm_destroy_host(struct nlm_host *host) BUG_ON(!list_empty(&host->h_lockowners)); BUG_ON(atomic_read(&host->h_count)); - /* - * Release NSM handle and unmonitor host. - */ nsm_unmonitor(host); + nsm_release(host->h_nsmhandle); clnt = host->h_rpcclnt; if (clnt != NULL) @@ -711,8 +710,7 @@ found: /* * Release an NSM handle */ -void -nsm_release(struct nsm_handle *nsm) +static void nsm_release(struct nsm_handle *nsm) { if (!nsm) return; -- cgit v1.2.3 From 67c6d107a689243979a2b5f15244b5261634a924 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Fri, 5 Dec 2008 19:02:45 -0500 Subject: NSM: Move nsm_find() to fs/lockd/mon.c The nsm_find() function sets up fresh nsm_handle entries. This is where we will store the "priv" cookie used to lookup nsm_handles during reboot recovery. The cookie will be constructed when nsm_find() creates a new nsm_handle. As much as possible, I would like to keep everything that handles a "priv" cookie in fs/lockd/mon.c so that all the smarts are in one source file. That organization should make it pretty simple to see how all this works. To me, it makes more sense than the current arrangement to keep nsm_find() with nsm_monitor() and nsm_unmonitor(). So, start reorganizing by moving nsm_find() into fs/lockd/mon.c. The nsm_release() function comes along too, since it shares the nsm_lock global variable. Signed-off-by: Chuck Lever Signed-off-by: J. Bruce Fields --- fs/lockd/host.c | 128 -------------------------------------------------------- 1 file changed, 128 deletions(-) (limited to 'fs/lockd/host.c') diff --git a/fs/lockd/host.c b/fs/lockd/host.c index 1d523c1a7b6..dbdeaa88d2f 100644 --- a/fs/lockd/host.c +++ b/fs/lockd/host.c @@ -32,12 +32,6 @@ static int nrhosts; static DEFINE_MUTEX(nlm_host_mutex); static void nlm_gc_hosts(void); -static struct nsm_handle *nsm_find(const struct sockaddr *sap, - const size_t salen, - const char *hostname, - const size_t hostname_len, - const int create); -static void nsm_release(struct nsm_handle *nsm); struct nlm_lookup_host_info { const int server; /* search for server|client */ @@ -106,43 +100,6 @@ static void nlm_clear_port(struct sockaddr *sap) } } -static void nlm_display_ipv4_address(const struct sockaddr *sap, char *buf, - const size_t len) -{ - const struct sockaddr_in *sin = (struct sockaddr_in *)sap; - snprintf(buf, len, "%pI4", &sin->sin_addr.s_addr); -} - -static void nlm_display_ipv6_address(const struct sockaddr *sap, char *buf, - const size_t len) -{ - const struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap; - - if (ipv6_addr_v4mapped(&sin6->sin6_addr)) - snprintf(buf, len, "%pI4", &sin6->sin6_addr.s6_addr32[3]); - else if (sin6->sin6_scope_id != 0) - snprintf(buf, len, "%pI6%%%u", &sin6->sin6_addr, - sin6->sin6_scope_id); - else - snprintf(buf, len, "%pI6", &sin6->sin6_addr); -} - -static void nlm_display_address(const struct sockaddr *sap, - char *buf, const size_t len) -{ - switch (sap->sa_family) { - case AF_INET: - nlm_display_ipv4_address(sap, buf, len); - break; - case AF_INET6: - nlm_display_ipv6_address(sap, buf, len); - break; - default: - snprintf(buf, len, "unsupported address family"); - break; - } -} - /* * Common host lookup routine for server & client */ @@ -635,88 +592,3 @@ nlm_gc_hosts(void) next_gc = jiffies + NLM_HOST_COLLECT; } - - -/* - * Manage NSM handles - */ -static LIST_HEAD(nsm_handles); -static DEFINE_SPINLOCK(nsm_lock); - -static struct nsm_handle *nsm_find(const struct sockaddr *sap, - const size_t salen, - const char *hostname, - const size_t hostname_len, - const int create) -{ - struct nsm_handle *nsm = NULL; - struct nsm_handle *pos; - - if (!sap) - return NULL; - - if (hostname && memchr(hostname, '/', hostname_len) != NULL) { - if (printk_ratelimit()) { - printk(KERN_WARNING "Invalid hostname \"%.*s\" " - "in NFS lock request\n", - (int)hostname_len, hostname); - } - return NULL; - } - -retry: - spin_lock(&nsm_lock); - list_for_each_entry(pos, &nsm_handles, sm_link) { - - if (hostname && nsm_use_hostnames) { - if (strlen(pos->sm_name) != hostname_len - || memcmp(pos->sm_name, hostname, hostname_len)) - continue; - } else if (!nlm_cmp_addr(nsm_addr(pos), sap)) - continue; - atomic_inc(&pos->sm_count); - kfree(nsm); - nsm = pos; - goto found; - } - if (nsm) { - list_add(&nsm->sm_link, &nsm_handles); - goto found; - } - spin_unlock(&nsm_lock); - - if (!create) - return NULL; - - nsm = kzalloc(sizeof(*nsm) + hostname_len + 1, GFP_KERNEL); - if (nsm == NULL) - return NULL; - - memcpy(nsm_addr(nsm), sap, salen); - nsm->sm_addrlen = salen; - nsm->sm_name = (char *) (nsm + 1); - memcpy(nsm->sm_name, hostname, hostname_len); - nsm->sm_name[hostname_len] = '\0'; - nlm_display_address((struct sockaddr *)&nsm->sm_addr, - nsm->sm_addrbuf, sizeof(nsm->sm_addrbuf)); - atomic_set(&nsm->sm_count, 1); - goto retry; - -found: - spin_unlock(&nsm_lock); - return nsm; -} - -/* - * Release an NSM handle - */ -static void nsm_release(struct nsm_handle *nsm) -{ - if (!nsm) - return; - if (atomic_dec_and_lock(&nsm->sm_count, &nsm_lock)) { - list_del(&nsm->sm_link); - spin_unlock(&nsm_lock); - kfree(nsm); - } -} -- cgit v1.2.3 From 7fefc9cb9d5f129c238d93166f705c96ca2e7e51 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Fri, 5 Dec 2008 19:03:31 -0500 Subject: NLM: Change nlm_host_rebooted() to take a single nlm_reboot argument Pass the nlm_reboot data structure directly from the NLMPROC_SM_NOTIFY XDR decoders to nlm_host_rebooted(). This eliminates some packing and unpacking of the NLMPROC_SM_NOTIFY results, and prepares for passing these results, including the "priv" cookie, directly to a lookup routine in fs/lockd/mon.c. This patch changes code organization but should not cause any behavioral change. Signed-off-by: Chuck Lever Signed-off-by: J. Bruce Fields --- fs/lockd/host.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) (limited to 'fs/lockd/host.c') diff --git a/fs/lockd/host.c b/fs/lockd/host.c index dbdeaa88d2f..ed103387964 100644 --- a/fs/lockd/host.c +++ b/fs/lockd/host.c @@ -444,31 +444,34 @@ void nlm_release_host(struct nlm_host *host) } } -/* - * We were notified that the host indicated by address &sin - * has rebooted. - * Release all resources held by that peer. +/** + * nlm_host_rebooted - Release all resources held by rebooted host + * @info: pointer to decoded results of NLM_SM_NOTIFY call + * + * We were notified that the specified host has rebooted. Release + * all resources held by that peer. */ -void nlm_host_rebooted(const struct sockaddr_in *sin, - const char *hostname, - unsigned int hostname_len, - u32 new_state) +void nlm_host_rebooted(const struct nlm_reboot *info) { + const struct sockaddr_in sin = { + .sin_family = AF_INET, + .sin_addr.s_addr = info->addr, + }; struct hlist_head *chain; struct hlist_node *pos; struct nsm_handle *nsm; struct nlm_host *host; - nsm = nsm_find((struct sockaddr *)sin, sizeof(*sin), - hostname, hostname_len, 0); + nsm = nsm_find((struct sockaddr *)&sin, sizeof(sin), + info->mon, info->len, 0); if (nsm == NULL) { dprintk("lockd: never saw rebooted peer '%.*s' before\n", - hostname_len, hostname); + info->len, info->mon); return; } dprintk("lockd: nlm_host_rebooted(%.*s, %s)\n", - hostname_len, hostname, nsm->sm_addrbuf); + info->len, info->mon, nsm->sm_addrbuf); /* When reclaiming locks on this peer, make sure that * we set up a new notification */ @@ -483,8 +486,8 @@ again: mutex_lock(&nlm_host_mutex); for (chain = nlm_hosts; chain < nlm_hosts + NLM_HOST_NRHASH; ++chain) { hlist_for_each_entry(host, pos, chain, h_hash) { if (host->h_nsmhandle == nsm - && host->h_nsmstate != new_state) { - host->h_nsmstate = new_state; + && host->h_nsmstate != info->state) { + host->h_nsmstate = info->state; host->h_state++; nlm_get_host(host); -- cgit v1.2.3 From 576df4634e37e46b441fefb91915184edb13bb94 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Fri, 5 Dec 2008 19:03:39 -0500 Subject: NLM: Decode "priv" argument of NLMPROC_SM_NOTIFY as an opaque The NLM XDR decoders for the NLMPROC_SM_NOTIFY procedure should treat their "priv" argument truly as an opaque, as defined by the protocol, and let the upper layers figure out what is in it. This will make it easier to modify the contents and interpretation of the "priv" argument, and keep knowledge about what's in "priv" local to fs/lockd/mon.c. For now, the NLM and NSM implementations should behave exactly as they did before. The formation of the address of the rebooted host in nlm_host_rebooted() may look a little strange, but it is the inverse of how nsm_init_private() forms the private cookie. Plus, it's going away soon anyway. Signed-off-by: Chuck Lever Signed-off-by: J. Bruce Fields --- fs/lockd/host.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'fs/lockd/host.c') diff --git a/fs/lockd/host.c b/fs/lockd/host.c index ed103387964..dc41e46ef74 100644 --- a/fs/lockd/host.c +++ b/fs/lockd/host.c @@ -453,9 +453,10 @@ void nlm_release_host(struct nlm_host *host) */ void nlm_host_rebooted(const struct nlm_reboot *info) { + __be32 *p = (__be32 *)&info->priv.data; const struct sockaddr_in sin = { .sin_family = AF_INET, - .sin_addr.s_addr = info->addr, + .sin_addr.s_addr = *p, }; struct hlist_head *chain; struct hlist_node *pos; -- cgit v1.2.3 From 8c7378fd2a5f22016542931b887a2ae98d146eaf Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Fri, 5 Dec 2008 19:03:54 -0500 Subject: NLM: Call nsm_reboot_lookup() instead of nsm_find() Invoke the newly introduced nsm_reboot_lookup() function in nlm_host_rebooted() instead of nsm_find(). This introduces just one behavioral change: debugging messages produced during reboot notification will now appear when the NLMDBG_MONITOR flag is set, but not when the NLMDBG_HOSTCACHE flag is set. Signed-off-by: Chuck Lever Signed-off-by: J. Bruce Fields --- fs/lockd/host.c | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) (limited to 'fs/lockd/host.c') diff --git a/fs/lockd/host.c b/fs/lockd/host.c index dc41e46ef74..230de93fc04 100644 --- a/fs/lockd/host.c +++ b/fs/lockd/host.c @@ -453,30 +453,14 @@ void nlm_release_host(struct nlm_host *host) */ void nlm_host_rebooted(const struct nlm_reboot *info) { - __be32 *p = (__be32 *)&info->priv.data; - const struct sockaddr_in sin = { - .sin_family = AF_INET, - .sin_addr.s_addr = *p, - }; struct hlist_head *chain; struct hlist_node *pos; struct nsm_handle *nsm; struct nlm_host *host; - nsm = nsm_find((struct sockaddr *)&sin, sizeof(sin), - info->mon, info->len, 0); - if (nsm == NULL) { - dprintk("lockd: never saw rebooted peer '%.*s' before\n", - info->len, info->mon); + nsm = nsm_reboot_lookup(info); + if (unlikely(nsm == NULL)) return; - } - - dprintk("lockd: nlm_host_rebooted(%.*s, %s)\n", - info->len, info->mon, nsm->sm_addrbuf); - - /* When reclaiming locks on this peer, make sure that - * we set up a new notification */ - nsm->sm_monitored = 0; /* Mark all hosts tied to this NSM state as having rebooted. * We run the loop repeatedly, because we drop the host table -- cgit v1.2.3 From 92fd91b998a5216a6d6606704e71d541a180216c Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Fri, 5 Dec 2008 19:04:01 -0500 Subject: NLM: Remove "create" argument from nsm_find() Clean up: nsm_find() now has only one caller, and that caller unconditionally sets the @create argument. Thus the @create argument is no longer needed. Since nsm_find() now has a more specific purpose, pick a more appropriate name for it. Signed-off-by: Chuck Lever Signed-off-by: J. Bruce Fields --- fs/lockd/host.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'fs/lockd/host.c') diff --git a/fs/lockd/host.c b/fs/lockd/host.c index 230de93fc04..e5a65df4c0c 100644 --- a/fs/lockd/host.c +++ b/fs/lockd/host.c @@ -159,8 +159,8 @@ static struct nlm_host *nlm_lookup_host(struct nlm_lookup_host_info *ni) atomic_inc(&nsm->sm_count); else { host = NULL; - nsm = nsm_find(ni->sap, ni->salen, - ni->hostname, ni->hostname_len, 1); + nsm = nsm_get_handle(ni->sap, ni->salen, + ni->hostname, ni->hostname_len); if (!nsm) { dprintk("lockd: nlm_lookup_host failed; " "no nsm handle\n"); -- cgit v1.2.3 From e6765b83977f07983c7a10e6bbb19d6c7bbfc3a4 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 11 Dec 2008 17:56:14 -0500 Subject: NSM: Remove include/linux/lockd/sm_inter.h Clean up: The include/linux/lockd/sm_inter.h header is nearly empty now. Remove it. Signed-off-by: Chuck Lever Signed-off-by: J. Bruce Fields --- fs/lockd/host.c | 1 - 1 file changed, 1 deletion(-) (limited to 'fs/lockd/host.c') diff --git a/fs/lockd/host.c b/fs/lockd/host.c index e5a65df4c0c..99d737bd432 100644 --- a/fs/lockd/host.c +++ b/fs/lockd/host.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include -- cgit v1.2.3