From 646a581fd680850b6d16fc1eed67199e606e6db4 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Tue, 21 Oct 2008 17:03:18 +0000 Subject: [PATCH] Add send (rfc3971 implementation) SVN-Revision: 13020 --- ipv6/send/Makefile | 55 +++ .../001-libnetfilter_queue_update.patch | 396 ++++++++++++++++++ .../patches/002-handle_max_bits_conf.patch | 59 +++ .../patches/003-keysig_8bytes_alignment.patch | 22 + .../004-always_link_with_ncurses.patch | 31 ++ 5 files changed, 563 insertions(+) create mode 100644 ipv6/send/Makefile create mode 100644 ipv6/send/patches/001-libnetfilter_queue_update.patch create mode 100644 ipv6/send/patches/002-handle_max_bits_conf.patch create mode 100644 ipv6/send/patches/003-keysig_8bytes_alignment.patch create mode 100644 ipv6/send/patches/004-always_link_with_ncurses.patch diff --git a/ipv6/send/Makefile b/ipv6/send/Makefile new file mode 100644 index 0000000000..2409abf472 --- /dev/null +++ b/ipv6/send/Makefile @@ -0,0 +1,55 @@ +# +# Copyright (C) 2008 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# +# $Id: $ + +include $(TOPDIR)/rules.mk + +PKG_NAME:=send +PKG_VERSION:=0.2-4 +PKG_RELEASE:=1 + +PKG_SOURCE:=$(PKG_NAME)d_$(PKG_VERSION).tar.gz +PKG_SOURCE_URL:=http://rfc.1924.fr/debian-mobisend/pool/main/s/sendd/ +PKG_MD5SUM:=57907178cad20fe3a21e2e449de1dc13 +PKG_BUILD_DIR:=$(BUILD_DIR)/sendd-0.2 + +include $(INCLUDE_DIR)/package.mk + +define Package/send + SECTION:=ipv6 + CATEGORY:=IPv6 + TITLE:=Secure Neighbor Discovery implementation + URL:=http://rfc.1924.fr/debian-mobisend.html + DEPENDS:=+kmod-ipv6 +ip6tables +libnetfilter-queue +libopenssl +libreadline +libncurses +endef + +define Package/send/description + DoCoMo's Open Source SEND project provides an implementation of RFC 3971 + Secure Neighbor Discovery (SEND). SEND cryptographically secures the + IPv6 neighbor discovery protocol, countering the threats discussed in + RFC 3756 (IPv6 Neighbor Discovery (ND) Trust Models and Threats). +endef + +define Package/send/conffiles +/etc/sendd/sendd.conf +/etc/sendd/params.conf +endef + +MAKE_FLAGS += \ + CC="$(TARGET_CC)" \ + CFLAGS="$(TARGET_CFLAGS) -I$(STAGING_DIR)/usr/include/libnetfilter_queue -I$(STAGING_DIR)/usr/include -I$(STAGING_DIR)/include" \ + LDFLAGS="$(TARGET_LDFLAGS) -lnfnetlink" \ + +define Package/send/install + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/sendd/sendd $(1)/usr/sbin/ + $(INSTALL_DIR) $(1)/etc/sendd/ + $(INSTALL_CONF) $(PKG_BUILD_DIR)/examples/sendd.conf $(1)/etc/sendd/ + $(INSTALL_CONF) $(PKG_BUILD_DIR)/examples/params.conf $(1)/etc/sendd/ +endef + +$(eval $(call BuildPackage,send)) diff --git a/ipv6/send/patches/001-libnetfilter_queue_update.patch b/ipv6/send/patches/001-libnetfilter_queue_update.patch new file mode 100644 index 0000000000..a06c6ae704 --- /dev/null +++ b/ipv6/send/patches/001-libnetfilter_queue_update.patch @@ -0,0 +1,396 @@ +Index: sendd-0.2/Makefile.config +=================================================================== +--- sendd-0.2.orig/Makefile.config 2008-05-27 16:53:49.238160776 +0200 ++++ sendd-0.2/Makefile.config 2008-05-27 16:53:52.866156255 +0200 +@@ -19,7 +19,7 @@ + #CC=gcc-4.0 + + # Where to install +-prefix=/usr ++prefix=$(DESTDIR)/usr + + # Set to "y" to build MT versions of sendd and cgatool + USE_THREADS=n +Index: sendd-0.2/README +=================================================================== +--- sendd-0.2.orig/README 2008-05-27 16:53:49.246168775 +0200 ++++ sendd-0.2/README 2008-05-27 16:53:52.866156255 +0200 +@@ -20,7 +20,7 @@ + o CONFIG_NETFILTER, CONFIG_IPV6, CONFIG_IP6_NF_QUEUE, CONFIG_IP6_NF_IPTABLES, + CONFIG_IP6_NF_FILTER enabled in your kernel config. + o netfilter ip6tables command +- o netfilter libipq development library and headers ++ o netfilter libnetfilter_queue development library and headers + + FreeBSD: + o NETGRAPH, NETGRAPH_BPF, NETGRAPH_ETHER, NETGRAPH_SOCKET enabled in +Index: sendd-0.2/cgatool/Makefile +=================================================================== +--- sendd-0.2.orig/cgatool/Makefile 2008-05-27 16:53:49.278164104 +0200 ++++ sendd-0.2/cgatool/Makefile 2008-05-27 16:53:52.866156255 +0200 +@@ -25,7 +25,7 @@ + + ifeq ($(USE_CONSOLE),y) + ifeq ($(USE_READLINE),y) +-LDLIBS += -lreadline -lncurses ++LDLIBS += -lreadline + endif + endif + +Index: sendd-0.2/sendd/Makefile +=================================================================== +--- sendd-0.2.orig/sendd/Makefile 2008-05-27 16:53:49.286173430 +0200 ++++ sendd-0.2/sendd/Makefile 2008-05-27 16:53:52.878156241 +0200 +@@ -37,7 +37,7 @@ + + ifeq ($(USE_CONSOLE),y) + ifeq ($(USE_READLINE),y) +-LDLIBS += -lreadline -lncurses ++LDLIBS += -lreadline + endif + endif + +Index: sendd-0.2/sendd/os-linux/Makefile +=================================================================== +--- sendd-0.2.orig/sendd/os-linux/Makefile 2008-05-27 16:53:49.294197424 +0200 ++++ sendd-0.2/sendd/os-linux/Makefile 2008-05-27 16:53:52.910156597 +0200 +@@ -1,23 +1,5 @@ + + OBJS += os/addr.o os/ipq.o os/rand.o os/snd_linux.o +-OSLIBS= -ldl -lipq ++OSLIBS= -lnetfilter_queue + +-OSEXTRA= os/sendd os/snd_upd_fw +- +-ETCINIT= /etc/init.d +-EXTRAINSTALL= $(ETCINIT)/sendd $(ETCINIT)/snd_upd_fw $(ETCINIT)/snd_fw_functions.sh +-EXTRAUNINSTALL=$(EXTRAINSTALL) +-EXTRACLEAN= os/sendd os/snd_upd_fw os/snd_fw_functions.sh +- +-$(ETCINIT)/%: os/% +- install $< $@ +- +-os/%: os/%.in +- sed "s/@etcinit@/\/etc\/init.d/g" $< > $@ +- +-os/%: os/%.in2 +- @./os/find_ip6tables.sh +- +-# Sometimes libipq.h is installed in include/libipq.h, other times it is +-# installed in include/libipq/libipq.h. This rule helps cpp to find it. +-os/ipq.o: CPPFLAGS += -I/usr/include/libipq -I/usr/local/include/libipq ++os/ipq.o: CPPFLAGS += -I/usr/include/libnetfilter_queue +Index: sendd-0.2/sendd/os-linux/ipq.c +=================================================================== +--- sendd-0.2.orig/sendd/os-linux/ipq.c 2008-05-27 16:53:49.306181136 +0200 ++++ sendd-0.2/sendd/os-linux/ipq.c 2008-05-27 16:55:57.602168158 +0200 +@@ -33,7 +33,7 @@ + #include + #include + #include +-#include ++#include + + #include "config.h" + #include +@@ -42,122 +42,170 @@ + #include "../sendd_local.h" + #include "snd_linux.h" + +-static struct ipq_handle *qh; +- + extern unsigned if_nametoindex(const char *); + +-static inline void +-process_pkt(ipq_packet_msg_t *pkt, struct sbuff *b) +-{ ++struct nfq_handle *h = NULL; ++struct nfq_q_handle *qh = NULL; ++ ++/* This is the default queue number used in our init script */ ++#define SND_DEFAULT_NFQUEUE_NUMBER 13 ++ ++/* The sbuff is must be made available to the callback function that will ++ handle the packet */ ++struct callback_data { ++ struct sbuff *b; ++}; ++ ++struct callback_data process_pkt_data; ++ ++/* nfqueue callback */ ++static int process_pkt(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, ++ struct nfq_data *nfa, void *data) ++{ ++ struct callback_data * d = (struct callback_data *)data; ++ struct nfqnl_msg_packet_hdr *ph; ++ struct sbuff *b = d->b; ++ char *pkt_data; + int in, ifidx; + +- b->data = pkt->payload; +- b->len = pkt->data_len; ++ b->len = nfq_get_payload(nfa, &pkt_data); ++ if (b->len == -1) { ++ applog(LOG_ERR, "%s: nfq_get_payload() failed.", __FUNCTION__); ++ return 0; ++ } ++ b->data = (unsigned char *)pkt_data; + +- if (*(pkt->indev_name)) { ++ if ((ifidx = nfq_get_indev(nfa)) && ifidx != 0) + in = 1; +- ifidx = if_nametoindex(pkt->indev_name); +- } else if (*(pkt->outdev_name)) { ++ else if ((ifidx = nfq_get_outdev(nfa)) && ifidx != 0) + in = 0; +- ifidx = if_nametoindex(pkt->outdev_name); +- } else { ++ else { + applog(LOG_ERR, "%s: pkt has neither indev nor outdev", + __FUNCTION__); + snd_put_buf(b); +- return; ++ return 0; + } + +- snd_recv_pkt(b, ifidx, in, pkt); +-} ++ /* Grab packet header to get its id later */ ++ ph = nfq_get_msg_packet_hdr(nfa); /* FIXME Check return value */ + +-static void +-ipq_recv_pkt(void) +-{ +- int r; +- struct sbuff *b = snd_get_buf(); ++ snd_recv_pkt(b, ifidx, in, (void *)ph); + +- if (b == NULL) { +- return; +- } +- if ((r = ipq_read(qh, b->head, b->rem, -1)) < 0) { +- applog(LOG_ERR, "%s: ipq_read(): %s", __FUNCTION__, +- ipq_errstr()); +- goto fail; +- } else if (r == 0) { +- /* timeout */ +- goto fail; +- } +- +- switch ((r = ipq_message_type(b->head))) { +- case NLMSG_ERROR: +- applog(LOG_ERR, "%s: nlmsg error: %s", __FUNCTION__, +- strerror(ipq_get_msgerr(b->head))); +- goto fail; +- case IPQM_PACKET: +- process_pkt(ipq_get_packet(b->head), b); +- return; +- default: +- break; +- } +- +-fail: +- snd_put_buf(b); ++ return 1; + } + + void +-linux_ipq_add_fds(fd_set *fds, int *maxfd) ++linux_nfq_add_fds(fd_set *fds, int *maxfd) + { +- FD_SET(qh->fd, fds); +- *maxfd = sendd_max(*maxfd, qh->fd); ++ int fd = nfnl_fd(nfq_nfnlh(h)); ++ ++ FD_SET(fd, fds); ++ *maxfd = sendd_max(*maxfd, fd); + } + + void +-linux_ipq_dispatch_fds(fd_set *fds) ++linux_nfq_dispatch_fds(fd_set *fds) + { +- if (FD_ISSET(qh->fd, fds)) { +- ipq_recv_pkt(); ++ int fd = nfnl_fd(nfq_nfnlh(h)); ++ int r; ++ ++ if (FD_ISSET(fd, fds)) { ++ struct sbuff *b = snd_get_buf(); ++ ++ if (b == NULL) { ++ return; ++ } ++ ++ if ((r = recv(fd, b->head, b->rem, 0)) && r <= 0) { ++ if (r < 0) /* not a timeout */ ++ applog(LOG_ERR, "%s: recv failed.", ++ __FUNCTION__); ++ snd_put_buf(b); ++ return; ++ } ++ ++ process_pkt_data.b = b; /* make sbuff available to ++ callback function */ ++ nfq_handle_packet(h, (char *)b->head, r); + } + } + + void + os_specific_deliver_pkt(void *p, struct sbuff *b, int drop, int changed) + { +- ipq_packet_msg_t *pkt = p; +- void *newpkt = NULL; ++ struct nfqnl_msg_packet_hdr *ph = (struct nfqnl_msg_packet_hdr *)p; ++ unsigned char *newpkt = NULL; + int plen = 0; ++ uint32_t id = 0; ++ ++ if (ph) ++ id = ntohl(ph->packet_id); + + if (changed && !drop) { +- newpkt = sbuff_data(b); ++ newpkt = (unsigned char *)b->data; + plen = b->len; + } + +- ipq_set_verdict(qh, pkt->packet_id, drop ? NF_DROP : NF_ACCEPT, +- plen, newpkt); ++ nfq_set_verdict(qh, id, drop ? NF_DROP : NF_ACCEPT, plen, newpkt); + snd_put_buf(b); + } + + int +-linux_ipq_init(void) ++linux_nfq_init(void) + { +- if ((qh = ipq_create_handle(0, PF_INET6)) == NULL) { +- applog(LOG_ERR, "%s: ipq_create_handle() failed: %s", +- __FUNCTION__, ipq_errstr()); +- return (-1); +- } +- if (ipq_set_mode(qh, IPQ_COPY_PACKET, SND_MAX_PKT) < 0) { +- applog(LOG_ERR, "%s: ipq_set_mode() failed: %s", +- __FUNCTION__, ipq_errstr()); +- if (errno == ECONNREFUSED) { +- applog(LOG_ERR, "%s: perhaps you need to modprobe " +- "ip6_queue?", __FUNCTION__); +- } +- return (-1); ++ struct nfnl_handle *nh; ++ u_int16_t nfqueue_num = SND_DEFAULT_NFQUEUE_NUMBER; ++ ++ /* Get netfilter queue connection handle */ ++ h = nfq_open(); ++ if (!h) { ++ applog(LOG_ERR, "%s: nfq_open() failed.", __FUNCTION__); ++ return -1; ++ } ++ ++ /* Unbinding existing nfqueue handlers for AF_INET6. We ignore the ++ return value: http://www.spinics.net/lists/netfilter/msg42063.html. ++ Note that this call is required, otherwise, nfq_bind_pf() fails. */ ++ nfq_unbind_pf(h, PF_INET6); ++ ++ if (nfq_bind_pf(h, PF_INET6) < 0) { ++ applog(LOG_ERR, "%s: nfq_bind_pf failed.\n", __FUNCTION__); ++ return -1; ++ } ++ ++ /* Binding this socket to queue number nfqueue_num and installing ++ our packet handler */ ++ qh = nfq_create_queue(h, nfqueue_num, ++ (nfq_callback *) &process_pkt, ++ (void *)&process_pkt_data); ++ if (!qh) { ++ applog(LOG_ERR, "%s: nfq_create_queue() failed.\n", ++ __FUNCTION__); ++ return -1; ++ } ++ ++ /* Asking for entire copy of queued packets */ ++ if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) { /* XXX was SND_MAX_PKT */ ++ fprintf(stderr, "nfq_set_mode() failed.\n"); ++ return -1; + } +- return (0); ++ ++ /* XXX - Check if we have an interest in setting queue length */ ++ ++ /* The netlink handle associated with our queue connection handle */ ++ nh = nfq_nfnlh(h); ++ ++ return 0; + } + + void +-linux_ipq_fini(void) ++linux_nfq_fini(void) + { +- ipq_destroy_handle(qh); ++ /* Remove the binding for our queue handler*/ ++ if (qh != NULL) ++ nfq_destroy_queue(qh); ++ ++ /* Close connection associated with our connection handler */ ++ if (h != NULL) ++ nfq_close(h); + } +Index: sendd-0.2/sendd/os-linux/snd_linux.c +=================================================================== +--- sendd-0.2.orig/sendd/os-linux/snd_linux.c 2008-05-27 16:53:49.314164060 +0200 ++++ sendd-0.2/sendd/os-linux/snd_linux.c 2008-05-27 16:53:52.914160667 +0200 +@@ -40,13 +40,13 @@ + void + os_specific_add_fds(fd_set *fds, int *maxfd) + { +- linux_ipq_add_fds(fds, maxfd); ++ linux_nfq_add_fds(fds, maxfd); + } + + void + os_specific_dispatch_fds(fd_set *fds) + { +- linux_ipq_dispatch_fds(fds); ++ linux_nfq_dispatch_fds(fds); + } + + int +@@ -59,7 +59,7 @@ + os_specific_init(void) + { + if (linux_rand_init() < 0 || +- linux_ipq_init() < 0) { ++ linux_nfq_init() < 0) { + return (-1); + } + return (0); +@@ -68,6 +68,6 @@ + void + os_specific_fini(void) + { +- linux_ipq_fini(); ++ linux_nfq_fini(); + linux_rand_fini(); + } +Index: sendd-0.2/sendd/os-linux/snd_linux.h +=================================================================== +--- sendd-0.2.orig/sendd/os-linux/snd_linux.h 2008-05-27 16:53:49.330169232 +0200 ++++ sendd-0.2/sendd/os-linux/snd_linux.h 2008-05-27 16:53:52.914160667 +0200 +@@ -33,10 +33,10 @@ + #ifndef _SEND_LINUX_H + #define _SEND_LINUX_H + +-extern void linux_ipq_add_fds(fd_set *, int *); +-extern void linux_ipq_dispatch_fds(fd_set *); +-extern int linux_ipq_init(void); +-extern void linux_ipq_fini(void); ++extern void linux_nfq_add_fds(fd_set *, int *); ++extern void linux_nfq_dispatch_fds(fd_set *); ++extern int linux_nfq_init(void); ++extern void linux_nfq_fini(void); + + extern void linux_rand_fini(void); + extern int linux_rand_init(void); diff --git a/ipv6/send/patches/002-handle_max_bits_conf.patch b/ipv6/send/patches/002-handle_max_bits_conf.patch new file mode 100644 index 0000000000..ad45ea5fd2 --- /dev/null +++ b/ipv6/send/patches/002-handle_max_bits_conf.patch @@ -0,0 +1,59 @@ +This patch allows one to specify a maximum number of bits +for the CGA and RSA key size. RFC specifies that an implementation +may optionnaly honor this setting (5.1.3). This is particularly +useful on embedded systems where both the entropy and the processing +power are limited. + +Index: sendd-0.2/sendd/config.c +=================================================================== +diff -urN sendd-0.2/sendd/config.c sendd-0.2.new/sendd/config.c +--- sendd-0.2/sendd/config.c 2008-04-18 16:21:46.000000000 +0200 ++++ sendd-0.2.new/sendd/config.c 2008-09-09 15:41:11.000000000 +0200 +@@ -82,6 +82,7 @@ + SND_CFS(snd_cga_params, NULL, 1), + SND_CFIB(snd_full_secure, 1, 0), + SND_CFII(snd_min_key_bits, 1024, "bits", 0), ++ SND_CFII(snd_max_key_bits, 2048, "bits", 0), + SND_CFII(snd_nonce_cache_gc_intvl, 2, "seconds", 0), + SND_CFII(snd_pfx_cache_gc_intvl, 40, "seconds", 0), + SND_CFS(snd_pkixip_conf, NULL, 0), +Index: sendd-0.2/sendd/sig_rfc3971.c +=================================================================== +diff -urN sendd-0.2/sendd/sig_rfc3971.c sendd-0.2.new/sendd/sig_rfc3971.c +--- sendd-0.2/sendd/sig_rfc3971.c 2008-04-18 16:21:46.000000000 +0200 ++++ sendd-0.2.new/sendd/sig_rfc3971.c 2008-09-10 11:14:35.000000000 +0200 +@@ -147,7 +147,7 @@ + EVP_MD_CTX ctx[1]; + EVP_PKEY *pub; + int rv = -1; +- int i, real_slen, min_bits; ++ int i, real_slen, min_bits, max_bits; + DEFINE_TIMESTAMP_VARS(); + + DBG_HEXDUMP(&dbg_cryptox, "key: ", key, klen); +@@ -164,6 +164,12 @@ + "minimum: %d)", EVP_PKEY_bits(pub), min_bits); + return (-1); + } ++ max_bits = snd_conf_get_int(snd_max_key_bits); ++ if (EVP_PKEY_bits(pub) > max_bits) { ++ DBG(&dbg_snd, "Peer key too strong: %d bits (configured " ++ "maximum: %d)", EVP_PKEY_bits(pub), max_bits); ++ return (-1); ++ } + + real_slen = EVP_PKEY_size(pub); + if (real_slen < slen) { +Index: sendd-0.2/sendd/snd_config.h +=================================================================== +diff -urN sendd-0.2/sendd/snd_config.h sendd-0.2.new/sendd/snd_config.h +--- sendd-0.2/sendd/snd_config.h 2008-04-18 16:21:46.000000000 +0200 ++++ sendd-0.2.new/sendd/snd_config.h 2008-09-09 15:09:45.000000000 +0200 +@@ -42,6 +42,7 @@ + snd_cga_params, + snd_full_secure, + snd_min_key_bits, ++ snd_max_key_bits, + snd_nonce_cache_gc_intvl, + snd_pfx_cache_gc_intvl, + snd_pkixip_conf, diff --git a/ipv6/send/patches/003-keysig_8bytes_alignment.patch b/ipv6/send/patches/003-keysig_8bytes_alignment.patch new file mode 100644 index 0000000000..3ebc1a876c --- /dev/null +++ b/ipv6/send/patches/003-keysig_8bytes_alignment.patch @@ -0,0 +1,22 @@ +This patch aligns the structure describing the RSA signature +option to 8 bytes. Before that, send was padding with N bytes +(4 on 32-bits architectures) using a cast in sendd/proto_sig.c : + + so = (struct snd_opt_sig *)(nd_so); + +which would align to the number of bytes representing a pointer +on your architecture. + +Index: sendd-0.2/sendd/snd_proto.h +============================================================ +--- sendd-0.2/sendd/snd_proto.h 2008-04-18 16:21:46.000000000 +0200 ++++ sendd-0.2.new/sendd/snd_proto.h 2008-10-05 16:08:34.000000000 +0200 +@@ -69,7 +69,7 @@ + uint32_t reserved; /* opt hdr + reserved */ + uint8_t keyhash[SND_KEYHASH_LEN]; + uint8_t sig[0]; +-}; ++} __attribute__((aligned(8))); + + struct snd_opt_timestamp { + uint8_t type; diff --git a/ipv6/send/patches/004-always_link_with_ncurses.patch b/ipv6/send/patches/004-always_link_with_ncurses.patch new file mode 100644 index 0000000000..0bb9c68dc2 --- /dev/null +++ b/ipv6/send/patches/004-always_link_with_ncurses.patch @@ -0,0 +1,31 @@ +We should always link with libncurses. This is not noticeable +when building the debian packages because we install libncurses5 +runtime library as a dependency. + +Index: sendd-0.2/cgatool/Makefile +=================================================================== +--- sendd-0.2/cgatool/Makefile 2008-10-05 16:26:28.000000000 +0200 ++++ sendd-0.2.new/cgatool/Makefile 2008-10-21 18:03:28.000000000 +0200 +@@ -17,7 +17,7 @@ + LDFLAGS= -L../libs/.libs + endif + +-LDLIBS += -lcrypto ++LDLIBS += -lcrypto -lncurses + + ifeq ($(USE_THREADS),y) + LDLIBS += -lpthread +Index: sendd-0.2/sendd/Makefile +=================================================================== +diff -urN sendd-0.2/sendd/Makefile sendd-0.2.new/sendd/Makefile +--- sendd-0.2/sendd/Makefile 2008-05-27 17:04:08.000000000 +0200 ++++ sendd-0.2.new/sendd/Makefile 2008-10-21 18:49:05.000000000 +0200 +@@ -28,7 +28,7 @@ + LDFLAGS= -L../libs/.libs + endif + +-LDLIBS += -lcrypto ++LDLIBS += -lcrypto -lncurses + LDLIBS += $(OSLIBS) + + ifeq ($(USE_THREADS),y) -- 2.30.2