diff options
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | dhcp.c | 183 | ||||
-rw-r--r-- | dhcp.h | 87 | ||||
-rw-r--r-- | forward.c | 12 | ||||
-rw-r--r-- | forward.h | 1 | ||||
-rw-r--r-- | helper.c | 52 | ||||
-rw-r--r-- | openvpn.8 | 38 | ||||
-rw-r--r-- | options.c | 27 | ||||
-rw-r--r-- | options.h | 3 | ||||
-rw-r--r-- | route.c | 10 | ||||
-rw-r--r-- | route.h | 13 | ||||
-rw-r--r-- | sample-config-files/client.conf | 2 | ||||
-rw-r--r-- | sample-config-files/server.conf | 30 | ||||
-rw-r--r-- | version.m4 | 2 |
14 files changed, 427 insertions, 34 deletions
diff --git a/Makefile.am b/Makefile.am index 16ed4e3..b4da771 100644 --- a/Makefile.am +++ b/Makefile.am @@ -79,6 +79,7 @@ openvpn_SOURCES = \ circ_list.h \ common.h \ crypto.c crypto.h \ + dhcp.c dhcp.h \ errlevel.h \ error.c error.h \ event.c event.h \ @@ -0,0 +1,183 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2008 Telethra, Inc. <sales@openvpn.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "syshead.h" + +#include "dhcp.h" +#include "socket.h" +#include "error.h" + +#include "memdbg.h" + +static int +get_dhcp_message_type (const struct dhcp *dhcp, const int optlen) +{ + const uint8_t *p = (uint8_t *) (dhcp + 1); + int i; + + for (i = 0; i < optlen; ++i) + { + const uint8_t type = p[i]; + const int room = optlen - i; + if (type == DHCP_END) /* didn't find what we were looking for */ + return -1; + else if (type == DHCP_PAD) /* no-operation */ + ; + else if (type == DHCP_MSG_TYPE) /* what we are looking for */ + { + if (room >= 3) + { + if (p[i+1] == 1) /* option length should be 1 */ + return p[i+2]; /* return message type */ + } + return -1; + } + else /* some other option */ + { + if (room >= 2) + { + const int len = p[i+1]; /* get option length */ + i += (len + 1); /* advance to next option */ + } + } + } + return -1; +} + +static in_addr_t +do_extract (struct dhcp *dhcp, const int optlen) +{ + uint8_t *p = (uint8_t *) (dhcp + 1); + int i; + in_addr_t ret = 0; + + for (i = 0; i < optlen; ++i) + { + const uint8_t type = p[i]; + const int room = optlen - i; + if (type == DHCP_END) + break; + else if (type == DHCP_PAD) + ; + else if (type == DHCP_ROUTER) + { + if (room >= 2) + { + const int len = p[i+1]; /* get option length */ + if (len <= (room-2)) + { + if (!ret && len >= 4 && (len & 3) == 0) + { + memcpy (&ret, p+i+2, 4); /* get router IP address */ + ret = ntohl (ret); + } + memset (p+i, DHCP_PAD, len+2); /* delete the router option by padding it out */ + } + i += (len + 1); /* advance to next option */ + } + } + else /* some other option */ + { + if (room >= 2) + { + const int len = p[i+1]; /* get option length */ + i += (len + 1); /* advance to next option */ + } + } + } + return ret; +} + +static uint16_t +udp_checksum (const uint8_t *buf, + const int len_udp, + const uint8_t *src_addr, + const uint8_t *dest_addr) +{ + uint16_t word16; + uint32_t sum = 0; + int i; + + /* make 16 bit words out of every two adjacent 8 bit words and */ + /* calculate the sum of all 16 bit words */ + for (i = 0; i < len_udp; i += 2){ + word16 = ((buf[i] << 8) & 0xFF00) + ((i + 1 < len_udp) ? (buf[i+1] & 0xFF) : 0); + sum += word16; + } + + /* add the UDP pseudo header which contains the IP source and destination addresses */ + for (i = 0; i < 4; i += 2){ + word16 =((src_addr[i] << 8) & 0xFF00) + (src_addr[i+1] & 0xFF); + sum += word16; + } + for (i = 0; i < 4; i += 2){ + word16 =((dest_addr[i] << 8) & 0xFF00) + (dest_addr[i+1] & 0xFF); + sum += word16; + } + + /* the protocol number and the length of the UDP packet */ + sum += (uint16_t) OPENVPN_IPPROTO_UDP + (uint16_t) len_udp; + + /* keep only the last 16 bits of the 32 bit calculated sum and add the carries */ + while (sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + + /* Take the one's complement of sum */ + return ((uint16_t) ~sum); +} + +in_addr_t +dhcp_extract_router_msg (struct buffer *ipbuf) +{ + struct dhcp_full *df = (struct dhcp_full *) BPTR (ipbuf); + const int optlen = BLEN (ipbuf) - (sizeof (struct openvpn_iphdr) + sizeof (struct openvpn_udphdr) + sizeof (struct dhcp)); + + if (optlen >= 0 + && df->ip.protocol == OPENVPN_IPPROTO_UDP + && df->udp.source == htons (BOOTPS_PORT) + && df->udp.dest == htons (BOOTPC_PORT) + && df->dhcp.op == BOOTREPLY + && get_dhcp_message_type (&df->dhcp, optlen) == DHCPACK) + { + /* get the router IP address while padding out all DHCP router options */ + const in_addr_t ret = do_extract (&df->dhcp, optlen); + + /* recompute the UDP checksum */ + df->udp.check = htons (udp_checksum ((uint8_t *) &df->udp, + sizeof (struct openvpn_udphdr) + sizeof (struct dhcp) + optlen, + (uint8_t *)&df->ip.saddr, + (uint8_t *)&df->ip.daddr)); + + if (ret) + { + struct gc_arena gc = gc_new (); + msg (D_ROUTE, "Extracted DHCP router address: %s", print_in_addr_t (ret, 0, &gc)); + gc_free (&gc); + } + + return ret; + } + else + return 0; +} @@ -0,0 +1,87 @@ +/* + * OpenVPN -- An application to securely tunnel IP networks + * over a single TCP/UDP port, with support for SSL/TLS-based + * session authentication and key exchange, + * packet encryption, packet authentication, and + * packet compression. + * + * Copyright (C) 2002-2008 Telethra, Inc. <sales@openvpn.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program (see the file COPYING included with this + * distribution); if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef DHCP_H +#define DHCP_H + +#include "common.h" +#include "buffer.h" +#include "proto.h" + +#pragma pack(1) + +/* DHCP Option types */ +#define DHCP_PAD 0 +#define DHCP_ROUTER 3 +#define DHCP_MSG_TYPE 53 /* message type (u8) */ +#define DHCP_END 255 + +/* DHCP Messages types */ +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPNAK 6 +#define DHCPRELEASE 7 +#define DHCPINFORM 8 + +/* DHCP UDP port numbers */ +#define BOOTPS_PORT 67 +#define BOOTPC_PORT 68 + +struct dhcp { +# define BOOTREQUEST 1 +# define BOOTREPLY 2 + uint8_t op; /* message op */ + + uint8_t htype; /* hardware address type (e.g. '1' = 10Mb Ethernet) */ + uint8_t hlen; /* hardware address length (e.g. '6' for 10Mb Ethernet) */ + uint8_t hops; /* client sets to 0, may be used by relay agents */ + uint32_t xid; /* transaction ID, chosen by client */ + uint16_t secs; /* seconds since request process began, set by client */ + uint16_t flags; + uint32_t ciaddr; /* client IP address, client sets if known */ + uint32_t yiaddr; /* 'your' IP address -- server's response to client */ + uint32_t siaddr; /* server IP address */ + uint32_t giaddr; /* relay agent IP address */ + uint8_t chaddr[16]; /* client hardware address */ + uint8_t sname[64]; /* optional server host name */ + uint8_t file[128]; /* boot file name */ + uint32_t magic; /* must be 0x63825363 (network order) */ +}; + +struct dhcp_full { + struct openvpn_iphdr ip; + struct openvpn_udphdr udp; + struct dhcp dhcp; +# define DHCP_OPTIONS_BUFFER_SIZE 256 + uint8_t options[DHCP_OPTIONS_BUFFER_SIZE]; +}; + +#pragma pack() + +in_addr_t dhcp_extract_router_msg (struct buffer *ipbuf); + +#endif @@ -31,6 +31,7 @@ #include "mss.h" #include "event.h" #include "ps.h" +#include "dhcp.h" #include "memdbg.h" @@ -976,6 +977,8 @@ process_ipv4_header (struct context *c, unsigned int flags, struct buffer *buf) if (!c->options.passtos) flags &= ~PIPV4_PASSTOS; #endif + if (!c->options.route_gateway_via_dhcp || !route_list_default_gateway_needed (c->c1.route_list)) + flags &= ~PIPV4_EXTRACT_DHCP_ROUTER; if (buf->len > 0) { @@ -1001,6 +1004,13 @@ process_ipv4_header (struct context *c, unsigned int flags, struct buffer *buf) /* possibly alter the TCP MSS */ if (flags & PIPV4_MSSFIX) mss_fixup (&ipbuf, MTU_TO_MSS (TUN_MTU_SIZE_DYNAMIC (&c->c2.frame))); + + /* possibly extract a DHCP router message */ + if (flags & PIPV4_EXTRACT_DHCP_ROUTER) + { + const in_addr_t dhcp_router = dhcp_extract_router_msg (&ipbuf); + route_list_add_default_gateway (c->c1.route_list, c->c2.es, dhcp_router); + } } } } @@ -1149,7 +1159,7 @@ process_outgoing_tun (struct context *c) * The --mssfix option requires * us to examine the IPv4 header. */ - process_ipv4_header (c, PIPV4_MSSFIX|PIPV4_OUTGOING, &c->c2.to_tun); + process_ipv4_header (c, PIPV4_MSSFIX|PIPV4_EXTRACT_DHCP_ROUTER|PIPV4_OUTGOING, &c->c2.to_tun); if (c->c2.to_tun.len <= MAX_RW_SIZE_TUN (&c->c2.frame)) { @@ -75,6 +75,7 @@ bool send_control_channel_string (struct context *c, const char *str, int msglev #define PIPV4_PASSTOS (1<<0) #define PIPV4_MSSFIX (1<<1) #define PIPV4_OUTGOING (1<<2) +#define PIPV4_EXTRACT_DHCP_ROUTER (1<<3) void process_ipv4_header (struct context *c, unsigned int flags, struct buffer *buf); @@ -54,6 +54,14 @@ print_opt_route_gateway (const in_addr_t route_gateway, struct gc_arena *gc) } static const char * +print_opt_route_gateway_dhcp (struct gc_arena *gc) +{ + struct buffer out = alloc_buf_gc (32, gc); + buf_printf (&out, "route-gateway dhcp"); + return BSTR (&out); +} + +static const char * print_opt_route (const in_addr_t network, const in_addr_t netmask, struct gc_arena *gc) { struct buffer out = alloc_buf_gc (128, gc); @@ -170,7 +178,7 @@ helper_client_server (struct options *o) if (o->client) msg (M_USAGE, "--server and --client cannot be used together"); - if (o->server_bridge_defined) + if (o->server_bridge_defined || o->server_bridge_proxy_dhcp) msg (M_USAGE, "--server and --server-bridge cannot be used together"); if (o->shared_secret_file) @@ -295,8 +303,19 @@ helper_client_server (struct options *o) * * ifconfig-pool 10.8.0.128 10.8.0.254 255.255.255.0 * push "route-gateway 10.8.0.4" + * + * OR + * + * server-bridge + * + * EXPANDS TO: + * + * mode server + * tls-server + * + * push "route-gateway dhcp" */ - else if (o->server_bridge_defined) + else if (o->server_bridge_defined | o->server_bridge_proxy_dhcp) { if (o->client) msg (M_USAGE, "--server-bridge and --client cannot be used together"); @@ -310,18 +329,29 @@ helper_client_server (struct options *o) if (dev != DEV_TYPE_TAP) msg (M_USAGE, "--server-bridge directive only makes sense with --dev tap"); - verify_common_subnet ("--server-bridge", o->server_bridge_ip, o->server_bridge_pool_start, o->server_bridge_netmask); - verify_common_subnet ("--server-bridge", o->server_bridge_pool_start, o->server_bridge_pool_end, o->server_bridge_netmask); - verify_common_subnet ("--server-bridge", o->server_bridge_ip, o->server_bridge_pool_end, o->server_bridge_netmask); + if (o->server_bridge_defined) + { + verify_common_subnet ("--server-bridge", o->server_bridge_ip, o->server_bridge_pool_start, o->server_bridge_netmask); + verify_common_subnet ("--server-bridge", o->server_bridge_pool_start, o->server_bridge_pool_end, o->server_bridge_netmask); + verify_common_subnet ("--server-bridge", o->server_bridge_ip, o->server_bridge_pool_end, o->server_bridge_netmask); + } o->mode = MODE_SERVER; o->tls_server = true; - o->ifconfig_pool_defined = true; - o->ifconfig_pool_start = o->server_bridge_pool_start; - o->ifconfig_pool_end = o->server_bridge_pool_end; - ifconfig_pool_verify_range (M_USAGE, o->ifconfig_pool_start, o->ifconfig_pool_end); - o->ifconfig_pool_netmask = o->server_bridge_netmask; - push_option (o, print_opt_route_gateway (o->server_bridge_ip, &o->gc), M_USAGE); + + if (o->server_bridge_defined) + { + o->ifconfig_pool_defined = true; + o->ifconfig_pool_start = o->server_bridge_pool_start; + o->ifconfig_pool_end = o->server_bridge_pool_end; + ifconfig_pool_verify_range (M_USAGE, o->ifconfig_pool_start, o->ifconfig_pool_end); + o->ifconfig_pool_netmask = o->server_bridge_netmask; + push_option (o, print_opt_route_gateway (o->server_bridge_ip, &o->gc), M_USAGE); + } + else if (o->server_bridge_proxy_dhcp) + { + push_option (o, print_opt_route_gateway_dhcp (&o->gc), M_USAGE); + } } else #endif /* P2MP_SERVER */ @@ -1214,11 +1214,18 @@ table (not supported on all OSes). address if OpenVPN is being run in client mode, and is undefined in server mode. .\"********************************************************* .TP -.B --route-gateway gw +.B --route-gateway gw|'dhcp' Specify a default gateway .B gw for use with .B --route. + +If +.B dhcp +is specified as the parameter, +the gateway address will be extracted from a DHCP +negotiation with the OpenVPN server-side LAN. +.\"********************************************************* .TP .B --route-metric m Specify a default metric @@ -2607,13 +2614,23 @@ if you are ethernet bridging. Use instead. .\"********************************************************* .TP -.B --server-bridge gateway netmask pool-start-IP pool-end-IP +.B --server-bridge [ gateway netmask pool-start-IP pool-end-IP ] A helper directive similar to .B --server which is designed to simplify the configuration of OpenVPN's server mode in ethernet bridging configurations. +If +.B --server-bridge +is used without any parameters, it will enable a DHCP-proxy +mode, where connecting OpenVPN clients will receive an IP +address for their TAP adapter from the DHCP server running +on the OpenVPN server-side LAN. +Note that only clients that support +the binding of a DHCP client with the TAP adapter (such as +Windows) can support this mode. + To configure ethernet bridging, you must first use your OS's bridging capability to bridge the TAP interface with the ethernet @@ -2662,6 +2679,23 @@ push "route-gateway 10.8.0.4" .LP .RE .fi + +In another example, +.B --server-bridge +(without parameters) expands as follows: + +.RS +.ft 3 +.nf +.sp +mode server +tls-server + +push "route-gateway dhcp" +.ft +.LP +.RE +.fi .\"********************************************************* .TP .B --push "option" @@ -169,7 +169,7 @@ static const char usage_message[] = " netmask default: 255.255.255.255\n" " gateway default: taken from --route-gateway or --ifconfig\n" " Specify default by leaving blank or setting to \"nil\".\n" - "--route-gateway gw : Specify a default gateway for use with --route.\n" + "--route-gateway gw|'dhcp' : Specify a default gateway for use with --route.\n" "--route-metric m : Specify a default metric for use with --route.\n" "--route-delay n [w] : Delay n seconds after connection initiation before\n" " adding routes (may be 0). If not specified, routes will\n" @@ -339,7 +339,7 @@ static const char usage_message[] = "\n" "Multi-Client Server options (when --mode server is used):\n" "--server network netmask : Helper option to easily configure server mode.\n" - "--server-bridge IP netmask pool-start-IP pool-end-IP : Helper option to\n" + "--server-bridge [IP netmask pool-start-IP pool-end-IP] : Helper option to\n" " easily configure ethernet bridging server mode.\n" "--push \"option\" : Push a config file option back to the peer for remote\n" " execution. Peer must specify --pull in its config file.\n" @@ -1226,6 +1226,7 @@ show_settings (const struct options *o) SHOW_INT (route_delay_window); SHOW_BOOL (route_delay_defined); SHOW_BOOL (route_nopull); + SHOW_BOOL (route_gateway_via_dhcp); if (o->routes) print_route_options (o->routes, D_SHOW_PARMS); @@ -1888,7 +1889,7 @@ static void options_postprocess_mutate_ce (struct options *o, struct connection_entry *ce) { #if P2MP_SERVER - if (o->server_defined || o->server_bridge_defined) + if (o->server_defined || o->server_bridge_defined || o->server_bridge_proxy_dhcp) { if (ce->proto == PROTO_TCPv4) ce->proto = PROTO_TCPv4_SERVER; @@ -4237,14 +4238,21 @@ add_option (struct options *options, else if (streq (p[0], "route-gateway") && p[1]) { VERIFY_PERMISSION (OPT_P_ROUTE_EXTRAS); - if (ip_addr_dotted_quad_safe (p[1]) || is_special_addr (p[1])) + if (streq (p[1], "dhcp")) { - options->route_default_gateway = p[1]; + options->route_gateway_via_dhcp = true; } else { - msg (msglevel, "route-gateway parm '%s' must be an IP address", p[1]); - goto err; + if (ip_addr_dotted_quad_safe (p[1]) || is_special_addr (p[1])) + { + options->route_default_gateway = p[1]; + } + else + { + msg (msglevel, "route-gateway parm '%s' must be an IP address", p[1]); + goto err; + } } } else if (streq (p[0], "route-metric") && p[1]) @@ -4395,6 +4403,11 @@ add_option (struct options *options, options->server_bridge_pool_start = pool_start; options->server_bridge_pool_end = pool_end; } + else if (streq (p[0], "server-bridge") && !p[1]) + { + VERIFY_PERMISSION (OPT_P_GENERAL); + options->server_bridge_proxy_dhcp = true; + } else if (streq (p[0], "push") && p[1]) { VERIFY_PERMISSION (OPT_P_PUSH); @@ -302,6 +302,7 @@ struct options bool route_delay_defined; struct route_option_list *routes; bool route_nopull; + bool route_gateway_via_dhcp; #ifdef ENABLE_OCC /* Enable options consistency check between peers */ @@ -340,6 +341,8 @@ struct options # define SF_NOPOOL (1<<0) unsigned int server_flags; + bool server_bridge_proxy_dhcp; + bool server_bridge_defined; in_addr_t server_bridge_ip; in_addr_t server_bridge_netmask; @@ -335,6 +335,16 @@ clear_route_list (struct route_list *rl) CLEAR (*rl); } +void +route_list_add_default_gateway (struct route_list *rl, + struct env_set *es, + const in_addr_t addr) +{ + rl->spec.remote_endpoint = addr; + rl->spec.remote_endpoint_defined = true; + setenv_route_addr (es, "vpn_gateway", rl->spec.remote_endpoint, -1); +} + bool init_route_list (struct route_list *rl, const struct route_option_list *opt, @@ -138,6 +138,10 @@ bool init_route_list (struct route_list *rl, in_addr_t remote_host, struct env_set *es); +void route_list_add_default_gateway (struct route_list *rl, + struct env_set *es, + const in_addr_t addr); + void add_routes (struct route_list *rl, const struct tuntap *tt, unsigned int flags, @@ -186,4 +190,13 @@ netbits_to_netmask (const int netbits) return mask; } +static inline bool +route_list_default_gateway_needed (const struct route_list *rl) +{ + if (!rl) + return false; + else + return !rl->spec.remote_endpoint_defined; +} + #endif diff --git a/sample-config-files/client.conf b/sample-config-files/client.conf index 9dd3a65..58b2038 100644 --- a/sample-config-files/client.conf +++ b/sample-config-files/client.conf @@ -100,7 +100,7 @@ key client.key # your server certificates with the nsCertType # field set to "server". The build-key-server # script in the easy-rsa folder will do this. -;ns-cert-type server +ns-cert-type server # If a tls-auth key is used on the server # then every client must also have the key. diff --git a/sample-config-files/server.conf b/sample-config-files/server.conf index f80ce8b..f483b6b 100644 --- a/sample-config-files/server.conf +++ b/sample-config-files/server.conf @@ -114,6 +114,18 @@ ifconfig-pool-persist ipp.txt # out unless you are ethernet bridging. ;server-bridge 10.8.0.4 255.255.255.0 10.8.0.50 10.8.0.100 +# Configure server mode for ethernet bridging +# using a DHCP-proxy, where clients talk +# to the OpenVPN server-side DHCP server +# to receive their IP address allocation +# and DNS server addresses. You must first use +# your OS's bridging capability to bridge the TAP +# interface with the ethernet NIC interface. +# Note: this mode only works on clients (such as +# Windows), where the client-side TAP adapter is +# bound to a DHCP client. +;server-bridge + # Push routes to the client to allow it # to reach other private subnets behind # the server. Remember that these @@ -170,22 +182,18 @@ ifconfig-pool-persist ipp.txt # all IP traffic such as web browsing and # and DNS lookups to go through the VPN # (The OpenVPN server machine may need to NAT -# the TUN/TAP interface to the internet in -# order for this to work properly). -# CAVEAT: May break client's network config if -# client's local DHCP server packets get routed -# through the tunnel. Solution: make sure -# client's local DHCP server is reachable via -# a more specific route than the default route -# of 0.0.0.0/0.0.0.0. -;push "redirect-gateway" +# or bridge the TUN/TAP interface to the internet +# in order for this to work properly). +;push "redirect-gateway def1 bypass-dhcp" # Certain Windows-specific network settings # can be pushed to clients, such as DNS # or WINS server addresses. CAVEAT: # http://openvpn.net/faq.html#dhcpcaveats -;push "dhcp-option DNS 10.8.0.1" -;push "dhcp-option WINS 10.8.0.1" +# The addresses below refer to the public +# DNS servers provided by opendns.com. +;push "dhcp-option DNS 208.67.222.222" +;push "dhcp-option DNS 208.67.220.220" # Uncomment this directive to allow different # clients to be able to "see" each other. @@ -1,5 +1,5 @@ dnl define the OpenVPN version -define(PRODUCT_VERSION,[2.1_rc9]) +define(PRODUCT_VERSION,[2.1_rc9a]) dnl define the TAP version define(PRODUCT_TAP_ID,[tap0901]) define(PRODUCT_TAP_WIN32_MIN_MAJOR,[9]) |