Hi,

On 12/11/17 00:07, Arne Schwabe wrote:
> This can be used to redirect all IPv6 traffic to the tun interface, 
> effectively black holing the IPv6 traffic. Without ICMPv6 error messages this 
> will result in timeouts when the server does not send error codes.
> block-ipv6 allows client side only blocking on all platforms that OpenVPN 
> supports IPv6. On Android it is only way to do sensible IPv6 blocking on 
> Android < 5.0 and broken devices (Samsung).
> ---
>  doc/openvpn.8         |  17 +++++
>  src/openvpn/forward.c | 170 
> ++++++++++++++++++++++++++++++++++++++++++++++++--
>  src/openvpn/forward.h |   3 +-
>  src/openvpn/misc.c    |   4 +-
>  src/openvpn/options.c |  17 +++++
>  src/openvpn/options.h |   1 +
>  src/openvpn/proto.h   |  19 ++++++
>  7 files changed, 224 insertions(+), 7 deletions(-)
> 
> diff --git a/doc/openvpn.8 b/doc/openvpn.8
> index a4189ac2..26b5f7de 100644
> --- a/doc/openvpn.8
> +++ b/doc/openvpn.8
> @@ -1232,6 +1232,23 @@ Like \-\-redirect\-gateway, but omit actually changing 
> the default
>  gateway.  Useful when pushing private subnets.
>  .\"*********************************************************
>  .TP
> +.B \-\-block\-ipv6
> +Instead of sending IPv6 packets over the VPN tunnel, all IPv6 packets are
> +answered with a ICMPv6 no route host message. This options is intended for
> +cases when IPv6 should be blocked and other options are not available.
> +
> +Following config block would send all IPv6 traffic to OpenVPN and answer all
> +requests with no route to host, effectively blocking IPv6.
> +
> +.B \-\-ifconfig-ipv6
> +fd15:53b6:dead::2/64  fd15:53b6:dead::1
> +.br
> +.B \-\-redirect\-gateway
> +ipv6
> +.br
> +.B \-\-block\-ipv6
> +.\"*********************************************************
> +.TP
>  .B \-\-tun\-mtu n
>  Take the TUN device MTU to be
>  .B n
> diff --git a/src/openvpn/forward.c b/src/openvpn/forward.c
> index 1b7455bb..21cce90b 100644
> --- a/src/openvpn/forward.c
> +++ b/src/openvpn/forward.c
> @@ -1198,7 +1198,7 @@ process_incoming_tun(struct context *c)
>           * The --passtos and --mssfix options require
>           * us to examine the IP header (IPv4 or IPv6).
>           */
> -        process_ip_header(c, PIPV4_PASSTOS|PIP_MSSFIX|PIPV4_CLIENT_NAT, 
> &c->c2.buf);
> +        process_ip_header(c, 
> PIPV4_PASSTOS|PIP_MSSFIX|PIPV4_CLIENT_NAT|PIPV6_IMCP_NOHOST, &c->c2.buf);
>  
>  #ifdef PACKET_TRUNCATION_CHECK
>          /* if (c->c2.buf.len > 1) --c->c2.buf.len; */
> @@ -1209,6 +1209,9 @@ process_incoming_tun(struct context *c)
>                                  &c->c2.n_trunc_pre_encrypt);
>  #endif
>  
> +    }
> +    if (c->c2.buf.len > 0)
> +    {
>          encrypt_sign(c, true);
>      }

I might not have a full understanding of this chunk: but could you
explain how this is related to this patch?


>      else
> @@ -1219,6 +1222,155 @@ process_incoming_tun(struct context *c)
>      gc_free(&gc);
>  }
>  
> +/**
> + *  Calculates an IPv6 checksum with a pseudo header as required by TCP, UDP 
> and ICMPv6
> + * @param payload the TCP, ICMPv6 or UDP packet
> + * @param len_payload length of payload
> + * @param src_addr
> + * @param dest_addr
> + * @return calculated checksum in host order
> + */
> +static uint16_t
> +ipv6_checksum(const uint8_t *payload,
> +              const int len_payload,
> +              const int next_header,
> +              const uint8_t *src_addr,
> +              const uint8_t *dest_addr)
> +{
> +    uint32_t sum = 0;
> +
> +    /* make 16 bit words out of every two adjacent 8 bit words and  */
> +    /* calculate the sum of all 16 bit words */
> +    for (int i = 0; i < len_payload; i += 2)
> +    {
> +        sum +=  (uint16_t) (((payload[i] << 8) & 0xFF00) + ((i + 1 < 
> len_payload) ? (payload[i + 1] & 0xFF) : 0));
> +
> +    }
> +
> +    /* add the pseudo header which contains the IP source and destination 
> addresses */
> +    for (int i = 0; i < 16; i += 2)
> +    {
> +        sum += (uint16_t) ((src_addr[i] << 8) & 0xFF00) + (src_addr[i+1] & 
> 0xFF);
> +
> +    }
> +    for (int i = 0; i < 16; i += 2)
> +    {
> +        sum += (uint16_t) ((dest_addr[i] << 8) & 0xFF00) + (dest_addr[i+1] & 
> 0xFF);
> +    }
> +
> +    /* the length of the payload */
> +    sum += (uint16_t) len_payload;
> +
> +    /* The next header */
> +    sum += (uint16_t) next_header;
> +
> +    /* 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);
> +}

The function above is basically a copy of udp_checksum() with very
little differences.

I'd NAK this patch because it would be better to convert these
differences in function arguments and then re-use the same code for both
UDP and ICMP checksum calculation.

This way we avoid code duplication.

Differences are:
- address length
- protocol number being added to `sum`

While there, please fix some codestyle issues that still exist in this
function.

> +/**
> + * Forges a IPv6 ICMP with a no route to host error code from the IPv6 
> packet in buf and sends it back via
> + * the tun device
> + */
> +void
> +ipv6_send_icmp_unreachable(struct context * c, struct buffer *buf)
> +{
> +#define ICMPV6LEN  1280
> +    struct openvpn_icmp6hdr icmp6out;
> +    CLEAR(icmp6out);
> +
> +    /*
> +     * Get a buffer to the ip packet, is_ipv6 automatically forwards
> +     * the buffer to the ip packet
> +     */
> +    struct buffer inputipbuf = *buf;

Please add an empty line here (like you've done everywhere else). I
believe that having an empty line between variables declarations and
actual code makes the whole thing easier to parse for poor brains like mine.

> +    is_ipv6(TUNNEL_TYPE(c->c1.tuntap), &inputipbuf);
> +
> +    if (BLEN(&inputipbuf) < (int) sizeof(struct openvpn_ipv6hdr))
> +    {
> +        return;
> +    }
> +
> +
> +    const struct openvpn_ipv6hdr* pip6 = (struct openvpn_ipv6hdr *) 
> BPTR(&inputipbuf);

there should be no space in the middle of a cast.

> +
> +    struct openvpn_ipv6hdr pip6out;
> +
> +    /* Copy version, traffic class, flow label from input packet */
> +    pip6out = *pip6;
> +
> +    pip6out.version_prio = pip6->version_prio;
> +
> +    pip6out.daddr = pip6->saddr;
> +    inet_pton( AF_INET6, c->options.ifconfig_ipv6_remote, &pip6out.saddr );
> +

no space between '(' and the first function argument.

> +    pip6out.nexthdr = OPENVPN_IPPROTO_ICMPV6;
> +
> +

one empty line is enough.

> +    /*
> +     * The ICMPv6 unreachable code worked best in my tests with Windows, 
> Linux and Android (arne)
> +     * Windows did not like the adminitively prohibited return code (no fast 
> fail)
> +     */
> +    icmp6out.icmp6_type = OPENVPN_ICMP6_DESTINATION_UNREACHABLE;
> +    icmp6out.icmp6_code = OPENVPN_ICMP6_DU_NOROUTE;
> +
> +    int icmpheader_len = sizeof(struct openvpn_ipv6hdr) + sizeof(struct 
> openvpn_icmp6hdr);
> +    int ethheader_len = sizeof(struct openvpn_ethhdr);
> +    int totalheader_len = icmpheader_len;

new line here too ? I'd leave this to your judgement from now on.

> +    if (TUNNEL_TYPE(c->c1.tuntap) == DEV_TYPE_TAP)
> +        totalheader_len += ethheader_len;
> +
> +    /*
> +     * Calculate size for payload, defined in the standard that the 
> resulting frame
> +     * should <= 1280 and have as much as possible of the original packet
> +     */
> +    int payload_len = min_int(min_int(ICMPV6LEN, TUN_MTU_SIZE(&c->c2.frame) 
> - icmpheader_len), buf_len(&inputipbuf));
> +
> +    pip6out.payload_len = htons(sizeof(struct openvpn_icmp6hdr) + 
> payload_len);
> +
> +    /* Construct the packet as outgoing packet back to the client */
> +    c->c2.to_tun = c->c2.buffers->aux_buf;
> +    ASSERT(buf_init(&c->c2.to_tun, totalheader_len));
> +
> +    /* Fill the end of the buffer with original packet */
> +    ASSERT(buf_safe(&c->c2.to_tun, payload_len));
> +    ASSERT(buf_copy_n(&c->c2.to_tun, &inputipbuf, payload_len));
> +
> +    /* ICMP Header, copy into buffer to allow checksum calculation */
> +    ASSERT(buf_write_prepend(&c->c2.to_tun, &icmp6out, sizeof(struct 
> openvpn_icmp6hdr)));
> +
> +    /* Calculate checksum over the packet and write to header */
> +    ((struct openvpn_icmp6hdr*) BPTR(&c->c2.to_tun))->icmp6_cksum = htons 
> (ipv6_checksum(BPTR(&c->c2.to_tun), BLEN(&c->c2.to_tun), 
> OPENVPN_IPPROTO_ICMPV6,
> +                                        (const uint8_t*) &pip6out.saddr, 
> (uint8_t*) &pip6out.daddr));
> +
> +    /* IPv6 Header */
> +    ASSERT(buf_write_prepend(&c->c2.to_tun, &pip6out, sizeof(struct 
> openvpn_ipv6hdr)));
> +

I see lots of asserts here and in other functions below.

Is it possible that a bridged/routed client could send a malformed IPv6
packet to convert this ASSERTs into a DoS for the VPN client?

Actually, any chance that this function is executed on the server? or is
this impossible?

> +    /* Working IPv6 block for TAP will also need the client to respond to 
> IPv6 nd with
> +     * its own (fake) adress
> +     */
> +    if (TUNNEL_TYPE(c->c1.tuntap) == DEV_TYPE_TAP)
> +    {
> +        if (BLEN(buf) < (int) sizeof (struct openvpn_ethhdr))
> +            return;
> +
> +        const struct openvpn_ethhdr* orig_ethhdr = (struct openvpn_ethhdr *) 
> BPTR(buf);
> +
> +        /* Copy frametype and reverse source/destination for the response */
> +        struct openvpn_ethhdr ethhdr;
> +        memcpy(ethhdr.source, orig_ethhdr->dest, OPENVPN_ETH_ALEN);
> +        memcpy(ethhdr.dest, orig_ethhdr->source, OPENVPN_ETH_ALEN);
> +        ethhdr.proto = htons(OPENVPN_ETH_P_IPV6);
> +        ASSERT(buf_write_prepend(&c->c2.to_tun, &ethhdr, sizeof(struct 
> openvpn_ethhdr)));
> +    }
> +}
> +
> +
>  void
>  process_ip_header(struct context *c, unsigned int flags, struct buffer *buf)
>  {
> @@ -1240,7 +1392,10 @@ process_ip_header(struct context *c, unsigned int 
> flags, struct buffer *buf)
>      {
>          flags &= ~PIPV4_EXTRACT_DHCP_ROUTER;
>      }
> -
> +    if (!c->options.block_ipv6)
> +    {
> +        flags &= ~PIPV6_IMCP_NOHOST;
> +    }
>      if (buf->len > 0)
>      {
>          /*
> @@ -1275,7 +1430,7 @@ process_ip_header(struct context *c, unsigned int 
> flags, struct buffer *buf)
>                  /* possibly do NAT on packet */
>                  if ((flags & PIPV4_CLIENT_NAT) && c->options.client_nat)
>                  {
> -                    const int direction = (flags & PIPV4_OUTGOING) ? 
> CN_INCOMING : CN_OUTGOING;
> +                    const int direction = (flags & PIP_OUTGOING) ? 
> CN_INCOMING : CN_OUTGOING;
>                      client_nat_transform(c->options.client_nat, &ipbuf, 
> direction);
>                  }
>                  /* possibly extract a DHCP router message */
> @@ -1295,6 +1450,13 @@ process_ip_header(struct context *c, unsigned int 
> flags, struct buffer *buf)
>                  {
>                      mss_fixup_ipv6(&ipbuf, 
> MTU_TO_MSS(TUN_MTU_SIZE_DYNAMIC(&c->c2.frame)));
>                  }
> +                if (!(flags & PIP_OUTGOING) && (flags & PIPV6_IMCP_NOHOST))
> +                {
> +                    ipv6_send_icmp_unreachable(c, buf);
> +                    // Drop the IPv6 packet

please don't use c++ style comments

> +                    buf->len=0;
> +                }
> +
>              }
>          }
>      }
> @@ -1478,7 +1640,7 @@ process_outgoing_tun(struct context *c)
>       * The --mssfix option requires
>       * us to examine the IP header (IPv4 or IPv6).
>       */
> -    process_ip_header(c, 
> PIP_MSSFIX|PIPV4_EXTRACT_DHCP_ROUTER|PIPV4_CLIENT_NAT|PIPV4_OUTGOING, 
> &c->c2.to_tun);
> +    process_ip_header(c, 
> PIP_MSSFIX|PIPV4_EXTRACT_DHCP_ROUTER|PIPV4_CLIENT_NAT|PIP_OUTGOING, 
> &c->c2.to_tun);
>  
>      if (c->c2.to_tun.len <= MAX_RW_SIZE_TUN(&c->c2.frame))
>      {
> diff --git a/src/openvpn/forward.h b/src/openvpn/forward.h
> index 9fde5a30..62199f9e 100644
> --- a/src/openvpn/forward.h
> +++ b/src/openvpn/forward.h
> @@ -251,9 +251,10 @@ bool send_control_channel_string(struct context *c, 
> const char *str, int msgleve
>  
>  #define PIPV4_PASSTOS         (1<<0)
>  #define PIP_MSSFIX            (1<<1)         /* v4 and v6 */
> -#define PIPV4_OUTGOING        (1<<2)
> +#define PIP_OUTGOING          (1<<2)
>  #define PIPV4_EXTRACT_DHCP_ROUTER (1<<3)
>  #define PIPV4_CLIENT_NAT      (1<<4)
> +#define PIPV6_IMCP_NOHOST     (1<<5)
>  
>  void process_ip_header(struct context *c, unsigned int flags, struct buffer 
> *buf);
>  
> diff --git a/src/openvpn/misc.c b/src/openvpn/misc.c
> index 001fe1c4..18f0cb5b 100644
> --- a/src/openvpn/misc.c
> +++ b/src/openvpn/misc.c
> @@ -150,7 +150,7 @@ openvpn_execve_check(const struct argv *a, const struct 
> env_set *es, const unsig
>      const int stat = openvpn_execve(a, es, flags);
>      int ret = false;
>  
> -    if (platform_system_ok(stat))
> +    if (platform_system_ok(stat)|| true)

space before the '||'

>      {
>          ret = true;
>      }
> @@ -204,7 +204,7 @@ openvpn_execve(const struct argv *a, const struct env_set 
> *es, const unsigned in
>              char *const *envp = (char *const *)make_env_array(es, true, &gc);
>              pid_t pid;
>  
> -            pid = fork();
> +            pid = vfork();

Can you explain how this is related to this patch?

>              if (pid == (pid_t)0) /* child side */
>              {
>                  execve(cmd, argv, envp);
> diff --git a/src/openvpn/options.c b/src/openvpn/options.c
> index 641a26e2..daa9ff05 100644
> --- a/src/openvpn/options.c
> +++ b/src/openvpn/options.c
> @@ -226,6 +226,8 @@ static const char usage_message[] =
>      "                  Add 'bypass-dns' flag to similarly bypass tunnel for 
> DNS.\n"
>      "--redirect-private [flags]: Like --redirect-gateway, but omit actually 
> changing\n"
>      "                  the default gateway.  Useful when pushing private 
> subnets.\n"
> +    "--block-ipv6     : (client only) Instead sending IPv6 to the server 
> generate\n"
> +    "                   ICMPv6 host unreachable messages.\n"
>      "--client-nat snat|dnat network netmask alias : on client add 1-to-1 NAT 
> rule.\n"
>  #ifdef ENABLE_PUSH_PEER_INFO
>      "--push-peer-info : (client only) push client info to server.\n"
> @@ -2084,6 +2086,15 @@ options_postprocess_verify_ce(const struct options 
> *options, const struct connec
>          msg(M_USAGE, "--lladdr can only be used in --dev tap mode");
>      }
>  
> +    if (options->block_ipv6 && !options->ifconfig_ipv6_remote)
> +    {
> +        msg(M_USAGE, "--block-ipv6 needs a valid --ifconfig-ipv6 
> configuration");
> +    }
> +

Instead of requiring the user to configure an ipv6 on its interface, you
could just fake a source address for the ICMP packets. This would make
it much easier for the user as he does not to come up with his own
"fake" ipv6 configuration.

> +    if (options->block_ipv6 && dev != DEV_TYPE_TUN)
> +    {
> +        msg(M_USAGE, "--block-ipv6 can be only used in --dev tun mode");
> +    }
>      /*
>       * Sanity check on MTU parameters
>       */
> @@ -2242,6 +2253,7 @@ options_postprocess_verify_ce(const struct options 
> *options, const struct connec
>          msg(M_USAGE, "TCP server mode allows at most one --remote address");
>      }
>  
> +

free gift?

>  #if P2MP_SERVER
>  
>      /*
> @@ -6360,6 +6372,11 @@ add_option(struct options *options,
>  #endif
>          options->routes->flags |= RG_ENABLE;
>      }
> +    else if (streq(p[0], "block-ipv6") && !p[1])
> +    {
> +        VERIFY_PERMISSION(OPT_P_ROUTE);
> +        options->block_ipv6 = true;
> +    }
>      else if (streq(p[0], "remote-random-hostname") && !p[1])
>      {
>          VERIFY_PERMISSION(OPT_P_GENERAL);
> diff --git a/src/openvpn/options.h b/src/openvpn/options.h
> index 496c1143..38e9e431 100644
> --- a/src/openvpn/options.h
> +++ b/src/openvpn/options.h
> @@ -340,6 +340,7 @@ struct options
>      bool route_delay_defined;
>      struct route_option_list *routes;
>      struct route_ipv6_option_list *routes_ipv6;                 /* IPv6 */
> +    bool block_ipv6;
>      bool route_nopull;
>      bool route_gateway_via_dhcp;
>      bool allow_pull_fqdn; /* as a client, allow server to push a FQDN for 
> certain parameters */
> diff --git a/src/openvpn/proto.h b/src/openvpn/proto.h
> index 57f25c90..7d300af9 100644
> --- a/src/openvpn/proto.h
> +++ b/src/openvpn/proto.h
> @@ -98,6 +98,7 @@ struct openvpn_iphdr {
>  #define OPENVPN_IPPROTO_IGMP 2  /* IGMP protocol */
>  #define OPENVPN_IPPROTO_TCP  6  /* TCP protocol */
>  #define OPENVPN_IPPROTO_UDP 17  /* UDP protocol */
> +#define OPENVPN_IPPROTO_ICMPV6 58 /* ICMPV6 protocol */

the defines above have all the values aligned. could you re-align them all?

>      uint8_t protocol;
>  
>      uint16_t check;
> @@ -120,6 +121,24 @@ struct openvpn_ipv6hdr {
>      struct  in6_addr daddr;
>  };
>  
> +/*
> + * ICMPv6 header
> + */
> +struct openvpn_icmp6hdr {
> +# define OPENVPN_ICMP6_DESTINATION_UNREACHABLE 1
> +# define OPENVPN_ND_ROUTER_SOLICIT   133
> +# define OPENVPN_ND_ROUTER_ADVERT    134
> +# define OPENVPN_ND_NEIGHBOR_SOLICIT 135
> +# define OPENVPN_ND_NEIGHBOR_ADVERT  136
> +# define OPENVPN_ND_INVERSE_SOLICIT  141
> +# define OPENVPN_ND_INVERSE_ADVERT   142
> +     uint8_t         icmp6_type;
> +# define OPENVPN_ICMP6_DU_NOROUTE 0
> +# define OPENVPN_ICMP6_DU_COMMUNICATION_PROHIBTED 1
> +     uint8_t         icmp6_code;
> +     uint16_t        icmp6_cksum;
> +     uint8_t         icmp6_dataun[4];
> +};
>  
>  /*
>   * UDP header
> 


Cheers,

-- 
Antonio Quartulli

Attachment: signature.asc
Description: OpenPGP digital signature

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Openvpn-devel mailing list
Openvpn-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to