From patchwork Fri Jul 26 08:20:53 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "plaisthos (Code Review)" X-Patchwork-Id: 3777 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7000:bb85:b0:5a1:d4fc:4ac6 with SMTP id gl5csp329372mab; Fri, 26 Jul 2024 01:21:38 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCWBB54uuGV2B9fyre5690jPdA284klrB3QiB5Z3pkBV0IDvshM8MWHEuMqMYL9jadu8tmqI2ERMBns=@openvpn.net X-Google-Smtp-Source: AGHT+IE8IkV0OW23INclPTp9Fk50h/xtExQ7+ejPqp0q1YA5yVLsZtSYCQfBObOVuLbwVyO9VB2e X-Received: by 2002:a05:6830:604:b0:708:b80d:f40 with SMTP id 46e09a7af769-7092fb4217dmr3534010a34.5.1721982097736; Fri, 26 Jul 2024 01:21:37 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1721982097; cv=none; d=google.com; s=arc-20160816; b=nE5pZE3W6ZQgTDPBzi125oPPTykN9ztwLUZT4aBQ2ihMmHyRWmLmE+nffdr6gGBPLD qJ9KdB15fDYdJ8lLS8tKXsF0vdtUjUs1kX8Aq/EWiewPNPf7iA3Dcuw6KMGWv2pt6/g3 xfQbi5R4qqGCSK5UPBmbC/ygx4dBjyJ8wPaNGv/RpWIk98I4V3oJ0oCoWwj6HM5i2IjO 2iajj1OfBOtHEjTcBid/LPqbOCnTcUILFcEYlUn/Ir9CV5GEF8yZXkOsnwe3gFiFLamb GOOq0LByPgqJ50Z0b6f4VgRplVm7zg+N53anEXcAvKZL0ZXIEEkuKQOAguRvtKbQbWMg dI0A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=errors-to:cc:reply-to:list-subscribe:list-help:list-post :list-archive:list-unsubscribe:list-id:precedence:subject:user-agent :mime-version:message-id:references:auto-submitted:to:date:from :dkim-signature:dkim-signature:dkim-signature; bh=1kxrxxXdrV5OFx5MObqxrOtMp+vc+6FFEgGpqJ3e92c=; fh=U7wEyxtwz2o5+UdevFSA47vNeG9knhWH0KV//QhD5a0=; b=Jmy/ZEX1d56Za4gjJsJ8oEXtudguW8MsX6oqfDFFe694iCIla4quOg0Aa1DkR1tg2Q VOtoBXSCToe37+vfpWjakfz9YD81OJy8gS2FIfLkzSMnNUJKtMo1NH9NW9OTJnGNGMAV x/OHvEUgzXQyLw7oeQ+H9BezVtc661r3TGT+9MtoHS/wCKtdwEB4UxJm8W9qw/DmErmH 6suxvGzx1xg0T4oxL8a4c6Lz9TlO6azl8nPBZ2AEW+N/p46+qbpAnstpttUmk5Z4ijiw OkRiZKiS0/tFrUqS5Uy312gYx8Paur96vWx5Z4UVuZuh8JYCSp90Dij2HlqMHRuBBEBH efCw==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=XfgotGzy; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=hqTgTFxf; dkim=neutral (body hash did not verify) header.i=@openvpn.net header.s=google header.b=WiAM+Jkv; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=openvpn.net; dara=fail header.i=@openvpn.net Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id 41be03b00d2f7-7a9f90fce4asi3454112a12.742.2024.07.26.01.21.37 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 26 Jul 2024 01:21:37 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=XfgotGzy; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=hqTgTFxf; dkim=neutral (body hash did not verify) header.i=@openvpn.net header.s=google header.b=WiAM+Jkv; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=openvpn.net; dara=fail header.i=@openvpn.net Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1sXGCP-00040S-Av; Fri, 26 Jul 2024 08:21:13 +0000 Received: from [172.30.29.67] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1sXGCN-000406-UV for openvpn-devel@lists.sourceforge.net; Fri, 26 Jul 2024 08:21:11 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Type:Content-Transfer-Encoding:MIME-Version :Message-ID:Reply-To:References:Subject:List-Unsubscribe:List-Id:Cc:To:Date: From:Sender:Content-ID:Content-Description:Resent-Date:Resent-From: Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:List-Help: List-Subscribe:List-Post:List-Owner:List-Archive; bh=cY4YMJMRaKHTLLbxE65d/79Pzze3O3kTJ1L3FjRNj9Y=; b=XfgotGzySDs5sEvTFayw4wrMKf 55NKZjZ6ui9lsWEadQvx+VbV0h7pikjdrX9glCTUBikwPRO0mpYu03l0nXIKqlkFmECJWRULlFzvz +s4n+n1eR7R+FLkzOCJz3Rie2ojam/esD/1PYUTnkaD8sx0vtgq0nDZs0wVaizBEAITw=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Type:Content-Transfer-Encoding:MIME-Version:Message-ID:Reply-To: References:Subject:List-Unsubscribe:List-Id:Cc:To:Date:From:Sender:Content-ID :Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To: Resent-Cc:Resent-Message-ID:In-Reply-To:List-Help:List-Subscribe:List-Post: List-Owner:List-Archive; bh=cY4YMJMRaKHTLLbxE65d/79Pzze3O3kTJ1L3FjRNj9Y=; b=h qTgTFxfAC7dgW82FJwXBUpSy4GlzYI1lq7rM5I1Db/iBupYuTh8uc6sh8Es5C8//hBjcz2SqAztir wALcurKZDGXv8q1tH26GDoEGGOANLWoYc1xokY7Xp5m6lB7B8xNnGvAlcOSoZjIh5OBN1xWCKdrTV Ve/fMzUAOrrepkHU=; Received: from mail-wm1-f43.google.com ([209.85.128.43]) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES128-GCM-SHA256:128) (Exim 4.95) id 1sXGCJ-0000fQ-2w for openvpn-devel@lists.sourceforge.net; Fri, 26 Jul 2024 08:21:11 +0000 Received: by mail-wm1-f43.google.com with SMTP id 5b1f17b1804b1-4266b1f1b21so13599635e9.1 for ; Fri, 26 Jul 2024 01:21:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=openvpn.net; s=google; t=1721982055; x=1722586855; darn=lists.sourceforge.net; h=user-agent:content-disposition:content-transfer-encoding :mime-version:message-id:reply-to:references:subject :list-unsubscribe:list-id:auto-submitted:cc:to:date:from:from:to:cc :subject:date:message-id:reply-to; bh=cY4YMJMRaKHTLLbxE65d/79Pzze3O3kTJ1L3FjRNj9Y=; b=WiAM+Jkv7nrDWgow28LnxHZvxzZOKhgFcr0W8gIY0OSLUJs+I9+Da1sCv1mkGGg3u/ jweWxasvTckN7Z9MkZ45fRd5ak+hnw1Q02KdKEpJq4EeyFi93flXNaVT1KsRfxWnUcOZ 8ghZqOSCBYpTANtaHq26PyNX9jHPzemjLeXBxTppqjIZd7P3nDWwkPK0cUlq5mY0T6Uq 2vb4ad58z5nnc549Y4JMymRBkWJH+ZJHgqeMIqIRmhiv9VmVrStdWsZuXa0xpb2MeXjo A5HySlMDmlil4vuzU2nk6ZEFyC3qAZstATt/mjAo08lUrU8mV14VsHDBiVxmcobzb5Kc xf4g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1721982055; x=1722586855; h=user-agent:content-disposition:content-transfer-encoding :mime-version:message-id:reply-to:references:subject :list-unsubscribe:list-id:auto-submitted:cc:to:date:from :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=cY4YMJMRaKHTLLbxE65d/79Pzze3O3kTJ1L3FjRNj9Y=; b=NnTDmFjCY7fNHtiwEAaRLmOJyf3ijSs3MB2jw3zPigNdp/xUUai2gTZaDSxcptXUc/ R+lGcqJDK7tCnYp90RJvwJk+VXGfUhjpR6XdM3GGozgsAxnET27MhNSxchbwtd1LTp+3 6HlTWnWJHIEJHUmtqrXvrvKUXvq3Zk4MVIR+f6eLsbIN0JRs0jS9cmZbSOSuitupdQUW RURFtebBsmTePK29cmu7WwEKl6St4dc5Du7t3YvxpwPoISvTGUdmY1jfW6bXX5d4nLYp VRnUyGCtVkv5G2t0slChYCDjhWGEVzmEaIPwevvnVGG6lu/ejzqSHSmJjAHIPhqx8q61 OOXw== X-Gm-Message-State: AOJu0YxfJ8GIAMs2ZoPNPpQ+ynbWVCWe5AB4W1mSScx9bAn/WJOrzG8o dHpk4nB5uRujcaF77M7hBnjrH0J7J6KXf13lsZGR/3n5zWt1BepUt5RKOxfrtIcVwe8drjbczs0 8 X-Received: by 2002:a05:600c:1c99:b0:427:ac40:d4b1 with SMTP id 5b1f17b1804b1-42806bda84amr30840075e9.27.1721982054925; Fri, 26 Jul 2024 01:20:54 -0700 (PDT) Received: from gerrit.openvpn.in (ec2-18-159-0-78.eu-central-1.compute.amazonaws.com. [18.159.0.78]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-42805730e47sm67787545e9.1.2024.07.26.01.20.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 26 Jul 2024 01:20:54 -0700 (PDT) From: "ralf_lici (Code Review)" X-Google-Original-From: "ralf_lici (Code Review)" X-Gerrit-PatchSet: 1 Date: Fri, 26 Jul 2024 08:20:53 +0000 To: plaisthos , flichtenheld Auto-Submitted: auto-generated X-Gerrit-MessageType: newchange X-Gerrit-Change-Id: I9766c68feaba71279f26c3310eee4b5ca215e6ac X-Gerrit-Change-Number: 685 X-Gerrit-Project: openvpn X-Gerrit-ChangeURL: X-Gerrit-Commit: 93f5f6b7e531e47b8da3ffc0ed0411c1d083ccb6 References: Message-ID: <192618d15a2b6ecb2eb10e078a5752ddb4723062-HTML@gerrit.openvpn.net> MIME-Version: 1.0 User-Agent: Gerrit/3.8.2 X-Spam-Score: -0.2 (/) X-Spam-Report: Spam detection software, running on the system "util-spamd-2.v13.lw.sourceforge.com", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: Attention is currently required from: flichtenheld, plaisthos. Hello plaisthos, flichtenheld, I'd like you to do a code review. Please visit Content analysis details: (-0.2 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: openvpn.net] 0.0 RCVD_IN_VALIDITY_SAFE_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. [209.85.128.43 listed in sa-trusted.bondedsender.org] 0.0 RCVD_IN_VALIDITY_RPBL_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. [209.85.128.43 listed in bl.score.senderscore.com] -0.0 RCVD_IN_MSPIKE_H2 RBL: Average reputation (+2) [209.85.128.43 listed in wl.mailspike.net] 0.0 RCVD_IN_DNSWL_BLOCKED RBL: ADMINISTRATOR NOTICE: The query to DNSWL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [209.85.128.43 listed in list.dnswl.org] 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record -0.0 SPF_PASS SPF: sender matches SPF record 0.0 WEIRD_PORT URI: Uses non-standard port number for HTTP 0.0 HTML_MESSAGE BODY: HTML included in message -0.1 DKIM_VALID_EF Message has a valid DKIM or DK signature from envelope-from domain -0.1 DKIM_VALID_AU Message has a valid DKIM or DK signature from author's domain -0.1 DKIM_VALID Message has at least one valid DKIM or DK signature 0.1 DKIM_SIGNED Message has a DKIM or DK signature, not necessarily valid 0.0 T_KAM_HTML_FONT_INVALID Test for Invalidly Named or Formatted Colors in HTML X-Headers-End: 1sXGCJ-0000fQ-2w Subject: [Openvpn-devel] [L] Change in openvpn[master]: Add support for HAProxy's PROXY protocol X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: ralf@mandelbit.com, arne-openvpn@rfc2549.org, openvpn-devel@lists.sourceforge.net, frank@lichtenheld.com Cc: openvpn-devel Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: =?utf-8?q?1805629100196542469?= X-GMAIL-MSGID: =?utf-8?q?1805629100196542469?= X-getmail-filter-classifier: gerrit message type newchange Attention is currently required from: flichtenheld, plaisthos. Hello plaisthos, flichtenheld, I'd like you to do a code review. Please visit http://gerrit.openvpn.net/c/openvpn/+/685?usp=email to review the following change. Change subject: Add support for HAProxy's PROXY protocol ...................................................................... Add support for HAProxy's PROXY protocol The PROXY protocol is a simple protocol that can be used to communicate connection information such as a client's address across multiple layers of NAT or TCP proxies. This commit adds support for the parsing of the v1 and v2 PROXY protocol header but does not yet parse the TLV data in the v2 header. Check if a PROXY protocol header was sent by the client at the beginning of the connection. If so, parse it, store the extracted informations and replace the proxy address with the real client address. Checking for the header is done only if the packet has an invalid length so that there's minimal overhead for the common case. Also add a small document file showing how to configure HAProxy to test the feature. Change-Id: I9766c68feaba71279f26c3310eee4b5ca215e6ac Signed-off-by: Ralf Lici --- M CMakeLists.txt A doc/proxy-protocol.txt M src/openvpn/Makefile.am M src/openvpn/init.c M src/openvpn/multi.c M src/openvpn/openvpn.h A src/openvpn/proxy_protocol.c A src/openvpn/proxy_protocol.h M src/openvpn/socket.c 9 files changed, 895 insertions(+), 7 deletions(-) git pull ssh://gerrit.openvpn.net:29418/openvpn refs/changes/85/685/1 diff --git a/CMakeLists.txt b/CMakeLists.txt index ad620fa..67cfd08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -490,6 +490,8 @@ src/openvpn/proto.h src/openvpn/proxy.c src/openvpn/proxy.h + src/openvpn/proxy_protocol.c + src/openvpn/proxy_protocol.h src/openvpn/ps.c src/openvpn/ps.h src/openvpn/push.c diff --git a/doc/proxy-protocol.txt b/doc/proxy-protocol.txt new file mode 100644 index 0000000..bd9b606 --- /dev/null +++ b/doc/proxy-protocol.txt @@ -0,0 +1,157 @@ +HAProxy PROXY protocol support for OpenVPN +========================================== + +The PROXY protocol is a simple protocol that can be used to retreive the +original source address of a connection that has been proxied. Every connection +is prepended with a header reporting the client IP address and port. The +protocol specification is available at: +https://www.haproxy.org/download/2.4/doc/proxy-protocol.txt + +Currently there are two versions of the protocol, a text based version (v1) and +a binary version (v2). + +Version 1 +--------- + +The text based version is a human readable protocol that is easy to debug. It +consists of a single line of text with the following fields: + +PROXY_SIGNATURE_V1 + single space + INET_PROTOCOL + single space + CLIENT_IP + +single space + PROXY_IP + single space + CLIENT_PORT + single space + +PROXY_PORT + "\r\n" + +The only supported INET_PROTOCOL in version 1 are "TCP4" and "TCP6". + +Version 2 +--------- + +The binary version is a more efficient protocol that is more suitable for +production use. It consists of a fixed length header followed by the original +connection data. + +A PROXY Protocol binary header for a IPv4 incoming address has the format: + 0 1 2 3 + 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | ++ + +| Proxy Protocol v2 Signature | ++ + +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|Version|Command| AF | Proto.| Address Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| IPv4 Source Address | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| IPv4 Destination Address | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Source Port | Destination Port | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | ++ + ++ TLVs + ++ + +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +A PROXY Protocol binary header for a IPv6 incoming address has the format: + 0 1 2 3 + 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | ++ + +| Proxy Protocol v2 Signature | ++ + +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|Version|Command| AF | Proto.| Address Length | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | ++ + +| | ++ IPv6 Source Address + +| | ++ + +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | ++ + +| | ++ IPv6 Destination Address + +| | ++ + +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Source Port | Destination Port | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| | ++ + ++ TLVs + ++ + +| | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +The fields are as follows: + +- Proxy Protocol v2 Signature: 12 bytes + The v2 signature of the protocol: "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A\x20". + +- Version: 4 bits + The version of the PROXY protocol (0x2). + +- Command: 4 bits + The command of the PROXY protocol can by one of the following: + - LOCAL: 0x0 + The connection was initiated by the proxy. + - PROXY: 0x1 + The connection was initiated by the client. + +- AF: 4 bits + The address family of the connection can be: AF_UNSPEC (0x0), AF_INET (0x1), + AF_INET6 (0x2) or AF_UNIX (0x3). + +- Proto: 4 bits + The protocol of the connection can be: UNSPEC (0x0), STREAM (0x1) or + DGRAM (0x2). + +- Address Length: 16 bits + The length of the rest of the header in bytes. + +Finally there may be a series of TLVs (Type-Length-Value) that can be used to +carry additional information. + +OpenVPN testing +--------------- + +Currently only TCP is supported. To test the PROXY protocol support in OpenVPN, +you can use HAProxy as a proxy with a configuration like this: + +global + log stdout format raw local0 + log 127.0.0.1 local0 debug + daemon + +defaults + log global + mode tcp + option tcplog + timeout connect 5000ms + timeout client 50000ms + timeout server 50000ms + +frontend openvpn_front + bind 10.10.20.1:1195 + default_backend openvpn_back + +backend openvpn_back + server openvpn_server 10.10.10.1:1194 send-proxy + # server openvpn_server 10.10.10.1:1194 send-proxy-v2 proxy-v2-options ssl,cert-cn,ssl-cipher,cert-sig,cert-key,authority,crc32c,unique-id + + +You can use the commented line to test the binary format and the TLV data. + +Note that you should set the keepalive option so that the connection is not +reset by HAProxy for inactivity. + +The OpenVPN TCP server should read and parse the header and use the information +to display the original source address in the logs. diff --git a/src/openvpn/Makefile.am b/src/openvpn/Makefile.am index 56cce9d..fc037ca 100644 --- a/src/openvpn/Makefile.am +++ b/src/openvpn/Makefile.am @@ -111,6 +111,7 @@ pool.c pool.h \ proto.c proto.h \ proxy.c proxy.h \ + proxy_protocol.c proxy_protocol.h \ ps.c ps.h \ push.c push.h \ pushlist.h \ diff --git a/src/openvpn/init.c b/src/openvpn/init.c index a49e563..cbce217 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -54,6 +54,7 @@ #include "mss.h" #include "mudp.h" #include "dco.h" +#include "proxy_protocol.h" #include "memdbg.h" @@ -680,6 +681,11 @@ c->c1.socks_proxy = NULL; c->c1.socks_proxy_owned = false; } + if (c->c1.proxy_protocol) + { + proxy_protocol_free(c->c1.proxy_protocol); + c->c1.proxy_protocol = NULL; + } } static void @@ -4774,7 +4780,7 @@ /* free up environmental variable store */ do_env_set_destroy(c); - /* close HTTP or SOCKS proxy */ + /* close HTTP or SOCKS proxy or PROXY protocol object */ uninit_proxy(c); /* garbage collect */ diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 03177bb..ac4652d 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -51,6 +51,7 @@ #include "ssl_util.h" #include "dco.h" #include "reflect_filter.h" +#include "proxy_protocol.h" /*#define MULTI_DEBUG_EVENT_LOOP*/ @@ -779,6 +780,8 @@ goto err; } + ALLOC_OBJ_CLEAR(mi->context.c1.proxy_protocol, struct proxy_protocol_info); + mi->context.c2.tls_multi->multi_state = CAS_NOT_CONNECTED; if (hash_n_elements(m->hash) >= m->max_clients) @@ -3207,6 +3210,57 @@ gc_free(&gc); } +void +multi_replace_proxy_addr(struct multi_context *m, struct multi_instance *mi) +{ + struct mroute_addr real = {0}; + struct hash *hash = m->hash; + struct gc_arena gc = gc_new(); + + if (!mroute_extract_openvpn_sockaddr(&real, &mi->context.c1.proxy_protocol->src, true)) + { + goto done; + } + + const uint32_t hv = hash_value(hash, &real); + struct hash_bucket *bucket = hash_bucket(hash, hv); + + /* if the new address is already in the hash table, the new client takes precedence */ + struct hash_element *he = hash_lookup_fast(hash, bucket, &real, hv); + if (he) + { + struct multi_instance *oldmi = (struct multi_instance *) he->value; + msg(D_MULTI_LOW, "Client's real address (behind proxy) matches " + "existing client address -- new client takes precedence"); + oldmi->did_real_hash = false; + multi_close_instance(m, oldmi, false); + he->key = &mi->real; + he->value = mi; + } + + clear_prefix(); + msg(D_MULTI_LOW, "Replacing proxy address: %s with client's real address: %s", + mroute_addr_print(&mi->real, &gc), + mroute_addr_print(&real, &gc)); + + /* remove proxy address from hash table before changing address */ + ASSERT(hash_remove(m->hash, &mi->real)); + ASSERT(hash_remove(m->iter, &mi->real)); + + mi->real = real; + generate_prefix(mi); + + ASSERT(hash_add(m->hash, &mi->real, mi, false)); + ASSERT(hash_add(m->iter, &mi->real, mi, false)); + +#ifdef ENABLE_MANAGEMENT + ASSERT(hash_add(m->cid_hash, &mi->context.c2.mda_context.cid, mi, true)); +#endif + +done: + gc_free(&gc); +} + /* * Called when an instance should be closed due to the * reception of a soft signal. @@ -3348,6 +3402,7 @@ struct mroute_addr src, dest; unsigned int mroute_flags; struct multi_instance *mi; + struct link_socket_info *lsi; bool ret = true; bool floated = false; @@ -3387,15 +3442,31 @@ } } + lsi = get_link_socket_info(c); + + /* check for PROXY protocol */ + if (lsi->proto == PROTO_TCP_SERVER && !c->c1.proxy_protocol->version && BLEN(&c->c2.buf) > 0) + { + if (!proxy_protocol_parse(c->c1.proxy_protocol, &c->c2.buf)) + { + /* set version to invalid to avoid checking again */ + c->c1.proxy_protocol->version = PROXY_PROTOCOL_VERSION_INVALID; + } + else + { + multi_replace_proxy_addr(m, m->pending); + /* avoid processing the packet as an openvpn one */ + buf_reset_len(&c->c2.buf); + } + } + if (BLEN(&c->c2.buf) > 0) { - struct link_socket_info *lsi; const uint8_t *orig_buf; /* decrypt in instance context */ perf_push(PERF_PROC_IN_LINK); - lsi = get_link_socket_info(c); orig_buf = c->c2.buf.data; if (process_incoming_link_part1(c, lsi, floated)) { diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h index e98c93e..ded6103 100644 --- a/src/openvpn/openvpn.h +++ b/src/openvpn/openvpn.h @@ -45,6 +45,7 @@ #include "pool.h" #include "plugin.h" #include "manage.h" +#include "proxy_protocol.h" /* * Our global key schedules, packaged thusly @@ -191,6 +192,8 @@ struct socks_proxy_info *socks_proxy; bool socks_proxy_owned; + struct proxy_protocol_info *proxy_protocol; + /* persist --ifconfig-pool db to file */ struct ifconfig_pool_persist *ifconfig_pool_persist; bool ifconfig_pool_persist_owned; diff --git a/src/openvpn/proxy_protocol.c b/src/openvpn/proxy_protocol.c new file mode 100644 index 0000000..10a68c2 --- /dev/null +++ b/src/openvpn/proxy_protocol.c @@ -0,0 +1,472 @@ +/* + * 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-2024 OpenVPN Inc + * + * 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; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "error.h" +#include "proxy_protocol.h" + +#define PROXY_PROTOCOL_V1_MAX_WORDS 6 +#define PROXY_PROTOCOL_V1_MAX_WORD_LEN 40 + +typedef enum +{ + PROXY_PROTOCOL_PARSING_STATE_INVALID = -1, + PROXY_PROTOCOL_PARSING_STATE_OK = 0, + PROXY_PROTOCOL_PARSING_STATE_IGNORE = 1, +} proxy_protocol_parsing_state_t; + +static const size_t PROXY_PROTOCOL_V2_ADDR_LEN_IPV4 = 12; +static const size_t PROXY_PROTOCOL_V2_ADDR_LEN_IPV6 = 36; +static const size_t PROXY_PROTOCOL_V2_ADDR_LEN_UNIX = 216; + +static proxy_protocol_header_t header; +static uint16_t header_len; +static proxy_protocol_parsing_state_t parsing_state = PROXY_PROTOCOL_PARSING_STATE_OK; + +proxy_protocol_version_t +proxy_protocol_version(const uint8_t *buf, const int buf_len) +{ + if (buf_len >= PROXY_PROTOCOL_V2_MIN_HDR_LEN + && memcmp(buf, PROXY_PROTOCOL_V2_SIG, PROXY_PROTOCOL_V2_SIG_LEN) == 0) + { + return PROXY_PROTOCOL_VERSION_2; + } + else if (buf_len >= PROXY_PROTOCOL_V1_MIN_HDR_LEN + && memcmp(buf, PROXY_PROTOCOL_V1_SIG, PROXY_PROTOCOL_V1_SIG_LEN) == 0) + { + return PROXY_PROTOCOL_VERSION_1; + } + return PROXY_PROTOCOL_VERSION_INVALID; +} + +int +proxy_protocol_header_len(const uint8_t *buf, const int buf_len, + const proxy_protocol_version_t version) +{ + memcpy(&header, buf, buf_len); + + switch (version) + { + case PROXY_PROTOCOL_VERSION_1: + { + char *end = memchr(header.v1.line, '\r', buf_len - 1); + if (!end || end[1] != '\n') + { + return -1; /* partial or invalid header */ + } + return (int)(end + 2 - header.v1.line); + } + + case PROXY_PROTOCOL_VERSION_2: + return PROXY_PROTOCOL_V2_MIN_HDR_LEN + ntohps(header.v2.len); + + default: + return -1; + } +} + +/* + * Parse a port number from a string. + * + * @param port_str - The string to parse. + * + * @return - The port number or -1 if the string is invalid. + */ +uint16_t +proxy_protocol_parse_port(const char *port_str) +{ + char *endptr; + errno = 0; + + long port = strtol(port_str, &endptr, 10); + if (errno != 0 || endptr == port_str || *endptr != '\0' || port <= 0 || port > 65535) + { + return -1; + } + return (uint16_t)port; +} + +/* + * Set the source and destination addresses and ports in the proxy protocol + * info structure for later use. + * + * @param ppi - The proxy protocol info structure to store the addresses. + * @param ver - The version of the PROXY protocol. + * @param fam - The address family (supports: AF_INET or AF_INET6). + * @param st - The socket type. + * @param src_addr - The source address. + * @param dst_addr - The destination address. + * @param src_port - The source port. + * @param dst_port - The destination port. + */ +void +proxy_protocol_set_addr(struct proxy_protocol_info *ppi, + const proxy_protocol_version_t ver, const int fam, const int st, + const void *src_addr, const void *dst_addr, + const uint16_t src_port, const uint16_t dst_port) +{ + + ppi->version = ver; + ppi->sock_type = st; + if (fam == AF_INET) + { + ppi->src.addr.sa.sa_family = AF_INET; + ppi->dst.addr.sa.sa_family = AF_INET; + memcpy(&ppi->src.addr.in4.sin_addr, (uint32_t *)src_addr, + sizeof(struct in_addr)); + memcpy(&ppi->dst.addr.in4.sin_addr, (uint32_t *)dst_addr, + sizeof(struct in_addr)); + ppi->src.addr.in4.sin_port = src_port; + ppi->dst.addr.in4.sin_port = dst_port; + msg(M_DEBUG, "PROXY protocol: SRC: %s:%u", + inet_ntoa(ppi->src.addr.in4.sin_addr), + ntohs(ppi->src.addr.in4.sin_port)); + msg(M_DEBUG, "PROXY protocol: DST: %s:%u", + inet_ntoa(ppi->dst.addr.in4.sin_addr), + ntohs(ppi->dst.addr.in4.sin_port)); + } + else if (fam == AF_INET6) + { + ppi->src.addr.sa.sa_family = AF_INET6; + ppi->dst.addr.sa.sa_family = AF_INET6; + memcpy(&ppi->src.addr.in6.sin6_addr, src_addr, sizeof(struct in6_addr)); + memcpy(&ppi->dst.addr.in6.sin6_addr, dst_addr, sizeof(struct in6_addr)); + ppi->src.addr.in6.sin6_port = src_port; + ppi->dst.addr.in6.sin6_port = dst_port; + + char ip6_str[INET6_ADDRSTRLEN]; + if (inet_ntop(AF_INET6, &ppi->src.addr.in6.sin6_addr, ip6_str, + INET6_ADDRSTRLEN)) + { + msg(M_DEBUG, "PROXY protocol: SRC: %s:%u", ip6_str, + ntohs(ppi->src.addr.in6.sin6_port)); + } + else + { + msg(M_NONFATAL, "PROXY protocol: could not parse source address"); + } + if (inet_ntop(AF_INET6, &ppi->dst.addr.in6.sin6_addr, ip6_str, + INET6_ADDRSTRLEN)) + { + msg(M_DEBUG, "PROXY protocol: DST: %s:%u", ip6_str, + ntohs(ppi->dst.addr.in6.sin6_port)); + } + else + { + msg(M_NONFATAL, + "PROXY protocol: could not parse destination address"); + } + } + else + { + msg(M_NONFATAL, "PROXY protocol: unsupported address family"); + } +} + +/* + * Split the v1 header line into words (space-separated) + * + * @param words - The array to store the words. + * @param line - The header line to split. + * @param len - The length of the header line. + * + * @return - The number of words found or -1 if an error occurred. + */ +int +proxy_protocol_v1_split_words(char words[][PROXY_PROTOCOL_V1_MAX_WORD_LEN], + const char *line, + int len) +{ + int word_num = 0; + const char *start = line; + for (int i = 0; i < len; ++i) + { + if (line[i] == ' ') + { + if (word_num < PROXY_PROTOCOL_V1_MAX_WORDS) + { + int word_len = (int)(line + i - start); + if (word_len < PROXY_PROTOCOL_V1_MAX_WORD_LEN) + { + memcpy(words[word_num], start, word_len); + words[word_num][word_len] = '\0'; + ++word_num; + } + else + { + msg(M_NONFATAL, "PROXY protocol v1: word too long"); + return -1; + } + } + start = line + i + 1; + } + } + return word_num; +} + +/* + * Parse the PROXY protocol v1 header line. + * + * @param ppi - The proxy protocol info structure to store the parsed data. + * @param line - The header line to parse. + * @param len - The length of the header line. + * + * @return - true if the header was successfully parsed. + */ +bool +proxy_protocol_parse_v1(struct proxy_protocol_info *ppi, + const char *line, + int len) +{ + const proxy_protocol_version_t version = PROXY_PROTOCOL_VERSION_1; + char words[PROXY_PROTOCOL_V1_MAX_WORDS][PROXY_PROTOCOL_V1_MAX_WORD_LEN]; + + char *end = memchr(line, '\r', len - 1); + if (!end || end[1] != '\n') /* partial or invalid header */ + { + return false; + } + *end = ' '; /* replace CRLF with space for easier splitting */ + int size = (int)(end - line + 1); + + int word_num = proxy_protocol_v1_split_words(words, line, size); + if (word_num < 3) + { + msg(M_NONFATAL, "PROXY protocol v1: could not split header"); + return false; + } + + if (strcmp(words[1], "UNKNOWN") == 0) + { + msg(M_DEBUG, "PROXY protocol v1: UNKNOWN protocol, ignoring header"); + return true; + } + else if (strcmp(words[1], "TCP4") == 0) + { + msg(M_DEBUG, "PROXY protocol v1: TCP4 protocol"); + struct in_addr ip4_src_addr, ip4_dst_addr; + if (inet_pton(AF_INET, words[2], &ip4_src_addr) != 1) + { + msg(M_NONFATAL, + "PROXY protocol v1: could not parse source address"); + return false; + } + if (inet_pton(AF_INET, words[3], &ip4_dst_addr) != 1) + { + msg(M_NONFATAL, + "PROXY protocol v1: could not parse destination address"); + return false; + } + proxy_protocol_set_addr(ppi, version, AF_INET, SOCK_STREAM, + &ip4_src_addr, &ip4_dst_addr, + ntohs(proxy_protocol_parse_port(words[4])), + ntohs(proxy_protocol_parse_port(words[5]))); + return true; + } + else if (strcmp(words[1], "TCP6") == 0) + { + msg(M_DEBUG, "PROXY protocol v1: TCP6 protocol"); + struct in6_addr ip6_src_addr, ip6_dst_addr; + if (inet_pton(AF_INET6, words[2], &ip6_src_addr) != 1) + { + msg(M_NONFATAL, + "PROXY protocol v1: could not parse source address"); + return false; + } + if (inet_pton(AF_INET6, words[3], &ip6_dst_addr) != 1) + { + msg(M_NONFATAL, + "PROXY protocol v1: could not parse destination address"); + return false; + } + proxy_protocol_set_addr(ppi, version, AF_INET6, SOCK_STREAM, + &ip6_src_addr, &ip6_dst_addr, + ntohs(proxy_protocol_parse_port(words[4])), + ntohs(proxy_protocol_parse_port(words[5]))); + return true; + } + else + { + msg(M_NONFATAL, "PROXY protocol v1: unsupported protocol"); + return false; + } +} + +/* + * Parse the PROXY protocol v2 header. + * + * @param ppi - The proxy protocol info structure to store the parsed data. + * @param header - The header to parse. + * + * @return - The number of bytes parsed or 0 if the header can be ignored. + */ +int +proxy_protocol_parse_v2(struct proxy_protocol_info *ppi) +{ + size_t addr_len = 0; + int pos = PROXY_PROTOCOL_V2_SIG_LEN + sizeof(header.v2.ver_cmd); + const proxy_protocol_version_t version = PROXY_PROTOCOL_VERSION_2; + + switch (header.v2.ver_cmd & PROXY_PROTOCOL_V2_CMD_MASK) + { + case PROXY_PROTOCOL_V2_LOCAL_CMD: + /* the receiver must accept this connection as valid and must use + * the real connection endpoints and discard the protocol block + * including the family which is ignored */ + msg(M_DEBUG, "PROXY protocol v2: LOCAL command"); + parsing_state = PROXY_PROTOCOL_PARSING_STATE_IGNORE; + break; + + case PROXY_PROTOCOL_V2_PROXY_CMD: + msg(M_DEBUG, "PROXY protocol v2: PROXY command"); + break; + + default: + msg(M_DEBUG, "PROXY protocol v2: UNSPEC or unknown command"); + parsing_state = PROXY_PROTOCOL_PARSING_STATE_INVALID; + } + + if (parsing_state != PROXY_PROTOCOL_PARSING_STATE_OK) + { + return pos; + } + pos += sizeof(header.v2.fam); + + switch (header.v2.fam) + { + case PROXY_PROTOCOL_V2_AF_INET | PROXY_PROTOCOL_V2_TP_STREAM: + msg(M_DEBUG, "PROXY protocol v2: TCP over IPv4."); + proxy_protocol_set_addr(ppi, version, AF_INET, SOCK_STREAM, + &header.v2.addr.ip4.src_addr, + &header.v2.addr.ip4.dst_addr, + header.v2.addr.ip4.src_port, + header.v2.addr.ip4.dst_port); + addr_len = PROXY_PROTOCOL_V2_ADDR_LEN_IPV4; + break; + + case PROXY_PROTOCOL_V2_AF_INET | PROXY_PROTOCOL_V2_TP_DGRAM: + msg(M_DEBUG, "PROXY protocol v2: UDP over IPv4"); + proxy_protocol_set_addr(ppi, version, AF_INET, SOCK_DGRAM, + &header.v2.addr.ip4.src_addr, + &header.v2.addr.ip4.dst_addr, + header.v2.addr.ip4.src_port, + header.v2.addr.ip4.dst_port); + addr_len = PROXY_PROTOCOL_V2_ADDR_LEN_IPV4; + break; + + case PROXY_PROTOCOL_V2_AF_INET6 | PROXY_PROTOCOL_V2_TP_STREAM: + msg(M_DEBUG, "PROXY protocol v2: TCP over IPv6"); + proxy_protocol_set_addr(ppi, version, AF_INET6, SOCK_STREAM, + header.v2.addr.ip6.src_addr, + header.v2.addr.ip6.dst_addr, + header.v2.addr.ip6.src_port, + header.v2.addr.ip6.dst_port); + addr_len = PROXY_PROTOCOL_V2_ADDR_LEN_IPV6; + break; + + case PROXY_PROTOCOL_V2_AF_INET6 | PROXY_PROTOCOL_V2_TP_DGRAM: + msg(M_DEBUG, "PROXY protocol v2: UDP over IPv6"); + proxy_protocol_set_addr(ppi, version, AF_INET6, SOCK_DGRAM, + header.v2.addr.ip6.src_addr, + header.v2.addr.ip6.dst_addr, + header.v2.addr.ip6.src_port, + header.v2.addr.ip6.dst_port); + addr_len = PROXY_PROTOCOL_V2_ADDR_LEN_IPV6; + break; + + case PROXY_PROTOCOL_V2_AF_UNIX | PROXY_PROTOCOL_V2_TP_STREAM: + msg(M_DEBUG, "PROXY protocol v2: AF_UNIX stream"); + proxy_protocol_set_addr(ppi, version, AF_UNIX, SOCK_STREAM, + header.v2.addr.unx.src_addr, + header.v2.addr.unx.dst_addr, 0, 0); + addr_len = PROXY_PROTOCOL_V2_ADDR_LEN_UNIX; + break; + + case PROXY_PROTOCOL_V2_AF_UNIX | PROXY_PROTOCOL_V2_TP_DGRAM: + msg(M_DEBUG, "PROXY protocol v2: AF_UNIX datagram"); + proxy_protocol_set_addr(ppi, version, AF_UNIX, SOCK_DGRAM, + header.v2.addr.unx.src_addr, + header.v2.addr.unx.dst_addr, 0, 0); + addr_len = PROXY_PROTOCOL_V2_ADDR_LEN_UNIX; + break; + + default: + msg(M_DEBUG, "PROXY protocol v2: UNSPEC or unknown address family"); + parsing_state = PROXY_PROTOCOL_PARSING_STATE_INVALID; + break; + } + return (int)(pos + sizeof(header.v2.len) + addr_len); +} + +bool +proxy_protocol_parse(struct proxy_protocol_info *ppi, const struct buffer *buf) +{ + const proxy_protocol_version_t version = proxy_protocol_version(BPTR(buf), BLEN(buf)); + + header_len = BLEN(buf); + memcpy(&header, BPTR(buf), header_len); + + switch (version) + { + case PROXY_PROTOCOL_VERSION_2: + { + if ((header.v2.ver_cmd & PROXY_PROTOCOL_V2_VER_MASK) == PROXY_PROTOCOL_V2_VER) + { + proxy_protocol_parse_v2(ppi); + if (parsing_state == PROXY_PROTOCOL_PARSING_STATE_IGNORE) + { + msg(M_DEBUG, "PROXY protocol v2: ignoring header"); + return true; + } + else if (parsing_state == PROXY_PROTOCOL_PARSING_STATE_INVALID) + { + return false; + } + msg(M_DEBUG, "PROXY protocol v2: header parsed"); + return true; + } + else + { + msg(M_NONFATAL, "PROXY protocol v2: expected version 2, got %d", + header.v2.ver_cmd & PROXY_PROTOCOL_V2_VER_MASK); + return false; + } + } + + case PROXY_PROTOCOL_VERSION_1: + msg(M_DEBUG, "PROXY protocol header v1: %.*s", BLEN(buf) - 2, header.v1.line); + return proxy_protocol_parse_v1(ppi, header.v1.line, BLEN(buf)); + + default: + return false; + } +} + +void +proxy_protocol_free(struct proxy_protocol_info *ppi) +{ + gc_free(&ppi->gc); +} diff --git a/src/openvpn/proxy_protocol.h b/src/openvpn/proxy_protocol.h new file mode 100644 index 0000000..8e9af03 --- /dev/null +++ b/src/openvpn/proxy_protocol.h @@ -0,0 +1,165 @@ +/* + * 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-2024 OpenVPN Inc + * + * 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; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef PROXY_PROTOCOL_H +#define PROXY_PROTOCOL_H + +#include "buffer.h" +#include "openvpn.h" +#include "socket.h" + +#include +#include + +/* https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt */ + +#define PROXY_PROTOCOL_V1_SIG "PROXY" +#define PROXY_PROTOCOL_V1_SIG_LEN 5 +#define PROXY_PROTOCOL_V1_MIN_HDR_LEN 8 +#define PROXY_PROTOCOL_V1_LINE_MAX_LEN 108 + +#define PROXY_PROTOCOL_V2_SIG "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A" +#define PROXY_PROTOCOL_V2_SIG_LEN 12 +#define PROXY_PROTOCOL_V2_MIN_HDR_LEN 16 + +#define PROXY_PROTOCOL_V2_VER_MASK 0xF0 +#define PROXY_PROTOCOL_V2_VER (0x2 << 4) + +#define PROXY_PROTOCOL_V2_CMD_MASK 0x0F +#define PROXY_PROTOCOL_V2_LOCAL_CMD (0x0 << 0) +#define PROXY_PROTOCOL_V2_PROXY_CMD (0x1 << 0) + +#define PROXY_PROTOCOL_V2_AF_MASK 0xF0 +#define PROXY_PROTOCOL_V2_AF_UNSPEC (0x0 << 4) +#define PROXY_PROTOCOL_V2_AF_INET (0x1 << 4) +#define PROXY_PROTOCOL_V2_AF_INET6 (0x2 << 4) +#define PROXY_PROTOCOL_V2_AF_UNIX (0x3 << 4) + +#define PROXY_PROTOCOL_V2_TP_MASK 0x0F +#define PROXY_PROTOCOL_V2_TP_UNSPEC (0x0 << 0) +#define PROXY_PROTOCOL_V2_TP_STREAM (0x1 << 0) +#define PROXY_PROTOCOL_V2_TP_DGRAM (0x2 << 0) + +typedef enum +{ + PROXY_PROTOCOL_VERSION_INVALID = -1, + + PROXY_PROTOCOL_VERSION_UNKNOWN = 0, + PROXY_PROTOCOL_VERSION_1, + PROXY_PROTOCOL_VERSION_2, +} proxy_protocol_version_t; + +/* HAProxy PROXY protocol header */ +typedef union +{ + struct + { + char line[PROXY_PROTOCOL_V1_LINE_MAX_LEN]; + } v1; + struct + { + uint8_t sig[PROXY_PROTOCOL_V2_SIG_LEN]; + uint8_t ver_cmd; + uint8_t fam; + uint16_t len; + union + { + struct + { + uint32_t src_addr; + uint32_t dst_addr; + uint16_t src_port; + uint16_t dst_port; + } ip4; + struct + { + uint8_t src_addr[16]; + uint8_t dst_addr[16]; + uint16_t src_port; + uint16_t dst_port; + } ip6; + struct + { + uint8_t src_addr[108]; + uint8_t dst_addr[108]; + } unx; + } addr; + } v2; +} proxy_protocol_header_t; + +struct proxy_protocol_info +{ + struct gc_arena gc; + + proxy_protocol_version_t version; + int sock_type; + struct openvpn_sockaddr src; + struct openvpn_sockaddr dst; +}; + +/* + * Check if the buffer contains a PROXY protocol header and return the version. + * + * @param buf - The buffer to check. + * @param buf_len - The length of the buffer. + * + * @return - The PROXY protocol version or PROXY_PROTOCOL_VERSION_INVALID if + * the buffer does not contain a valid PROXY protocol header. + */ +proxy_protocol_version_t +proxy_protocol_version(const uint8_t *buf, int buf_len); + +/* + * Get the length of the PROXY protocol header. + * + * @param buf - The buffer to check. + * @param buf_len - The length of the buffer. + * @param version - The PROXY protocol version. + * + * @return - The length of the header or -1 if the header is partial or invalid. + */ +int +proxy_protocol_header_len(const uint8_t *buf, int len, + proxy_protocol_version_t version); + +/* + * Parse the PROXY protocol header. + * + * @param ppi - The proxy protocol info structure to store the parsed data. + * @param buf - The buffer containing the header. + * @param version - The version of the PROXY protocol. + * + * @return - true if the header was successfully parsed. + */ +bool +proxy_protocol_parse(struct proxy_protocol_info *ppi, const struct buffer *buf); + +/* + * Free the allocated memory. + * + * @param ppi - The proxy protocol info structure. + */ +void +proxy_protocol_free(struct proxy_protocol_info *ppi); + +#endif /* PROXY_PROTOCOL_H */ diff --git a/src/openvpn/socket.c b/src/openvpn/socket.c index 17c5e76..7fb382c 100644 --- a/src/openvpn/socket.c +++ b/src/openvpn/socket.c @@ -39,6 +39,7 @@ #include "manage.h" #include "openvpn.h" #include "forward.h" +#include "proxy_protocol.h" #include "memdbg.h" @@ -2680,10 +2681,20 @@ if (sb->len < 1 || sb->len > sb->maxlen) { - msg(M_WARN, "WARNING: Bad encapsulated packet length from peer (%d), which must be > 0 and <= %d -- please ensure that --tun-mtu or --link-mtu is equal on both peers -- this condition could also indicate a possible active attack on the TCP link -- [Attempting restart...]", sb->len, sb->maxlen); - stream_buf_reset(sb); - sb->error = true; - return false; + /* check if it's a PROXY protocol header */ + + /* undo the reading of net_size */ + ASSERT(buf_prepend(&sb->buf, sizeof(net_size))); + const proxy_protocol_version_t pp_version = proxy_protocol_version(BPTR(&sb->buf), BLEN(&sb->buf)); + const int pp_len = proxy_protocol_header_len(BPTR(&sb->buf), BLEN(&sb->buf), pp_version); + if (pp_version <= 0 || pp_len < 0 || pp_len > sb->buf.len) + { + msg(M_WARN, "WARNING: Bad encapsulated packet length from peer (%d), which must be > 0 and <= %d -- please ensure that --tun-mtu or --link-mtu is equal on both peers -- this condition could also indicate a possible active attack on the TCP link -- [Attempting restart...]", sb->len, sb->maxlen); + stream_buf_reset(sb); + sb->error = true; + return false; + } + sb->len = pp_len; } }