Repository: pires/go-proxyproto
Pull request #20 (2020-04-07 17:08:43)

Add support for Azure extensions
@pires do you accept contributions to tlvparse extension package? If so I'd like to add Azure support, which is fairly similar to AWS.

The support is modeled on `aws.go` under `tlvparse`. The Azure spec is similar but
encodes `uint32` instead of a string. Because of that I've opted for an
additional boolean indicating if given value was found -- the zero value of
`uint32` is technically a valid header value.

Let me know what do you think.

BTW We currently have a similar implementation (diff func names, etc. but the logic is the same) in our codebase but we don't run it in production yet. I've tested it on a dev env with Azure Private Link set up, and the header is parsed correctly.

---

Azure spec: https://docs.microsoft.com/en-us/azure/private-link/private-link-service-overview#getting-connection-information-using-tcp-proxy-v2



Patch:
diff --git a/tlvparse/azure.go b/tlvparse/azure.go
new file mode 100644
index 0000000..d1a151c
--- /dev/null
+++ b/tlvparse/azure.go
@@ -0,0 +1,48 @@
+// Azure's application extension to TLVs for Private Link Services
+// https://docs.microsoft.com/en-us/azure/private-link/private-link-service-overview#getting-connection-information-using-tcp-proxy-v2
+
+package tlvparse
+
+import (
+	"encoding/binary"
+
+	"github.com/pires/go-proxyproto"
+)
+
+const (
+	// Azure's extension
+	PP2_TYPE_AZURE                           = 0xEE
+	PP2_SUBTYPE_AZURE_PRIVATEENDPOINT_LINKID = 0x01
+)
+
+// IsAzurePrivateEndpointLinkID returns true if given TLV matches Azure Private Endpoint LinkID format
+func isAzurePrivateEndpointLinkID(tlv proxyproto.TLV) bool {
+	return tlv.Type == PP2_TYPE_AZURE && tlv.Length == 5 && tlv.Value[0] == PP2_SUBTYPE_AZURE_PRIVATEENDPOINT_LINKID
+}
+
+// AzurePrivateEndpointLinkID returns linkID if given TLV matches Azure Private Endpoint LinkID format
+//
+// Format description:
+//	Field	Length (Octets)	Description
+//	Type	1	PP2_TYPE_AZURE (0xEE)
+//	Length	2	Length of value
+//	Value	1	PP2_SUBTYPE_AZURE_PRIVATEENDPOINT_LINKID (0x01)
+//			4	UINT32 (4 bytes) representing the LINKID of the private endpoint. Encoded in little endian format.
+func azurePrivateEndpointLinkID(tlv proxyproto.TLV) (uint32, error) {
+	if !isAzurePrivateEndpointLinkID(tlv) {
+		return 0, proxyproto.ErrIncompatibleTLV
+	}
+	linkID := binary.LittleEndian.Uint32(tlv.Value[1:])
+	return linkID, nil
+}
+
+// FindAzurePrivateEndpointLinkID returns the first Azure Private Endpoint LinkID if it exists in the TLV collection
+// and a boolean indicating if it was found.
+func FindAzurePrivateEndpointLinkID(tlvs []proxyproto.TLV) (uint32, bool) {
+	for _, tlv := range tlvs {
+		if linkID, err := azurePrivateEndpointLinkID(tlv); err == nil {
+			return linkID, true
+		}
+	}
+	return 0, false
+}


Test patch:
diff --git a/tlvparse/azure_test.go b/tlvparse/azure_test.go
new file mode 100644
index 0000000..c71adfd
--- /dev/null
+++ b/tlvparse/azure_test.go
@@ -0,0 +1,120 @@
+package tlvparse
+
+import (
+	"testing"
+
+	"github.com/pires/go-proxyproto"
+)
+
+func TestFindAzurePrivateEndpointLinkID(t *testing.T) {
+	tests := []struct {
+		name       string
+		tlvs       []proxyproto.TLV
+		wantLinkID uint32
+		wantFound  bool
+	}{
+		{
+			name:       "nil TLVs",
+			tlvs:       nil,
+			wantLinkID: 0,
+			wantFound:  false,
+		},
+		{
+			name:       "empty TLVs",
+			tlvs:       []proxyproto.TLV{},
+			wantLinkID: 0,
+			wantFound:  false,
+		},
+		{
+			name: "AWS VPC endpoint ID",
+			tlvs: []proxyproto.TLV{
+				{
+					Type:   0xEA,
+					Length: 12,
+					Value:  []byte{0x01, 0x76, 0x70, 0x63, 0x65, 0x2d, 0x61, 0x62, 0x63, 0x31, 0x32, 0x33},
+				},
+			},
+			wantLinkID: 0,
+			wantFound:  false,
+		},
+		{
+			name: "Azure but wrong subtype",
+			tlvs: []proxyproto.TLV{
+				{
+					Type:   0xEE,
+					Length: 5,
+					Value:  []byte{0x02, 0x01, 0x01, 0x01, 0x01},
+				},
+			},
+			wantLinkID: 0,
+			wantFound:  false,
+		},
+		{
+			name: "Azure but wrong length",
+			tlvs: []proxyproto.TLV{
+				{
+					Type:   0xEE,
+					Length: 3,
+					Value:  []byte{0x02, 0x01, 0x01},
+				},
+			},
+			wantLinkID: 0,
+			wantFound:  false,
+		},
+		{
+			name: "Azure link ID",
+			tlvs: []proxyproto.TLV{
+				{
+					Type:   0xEE,
+					Length: 5,
+					Value:  []byte{0x1, 0xc1, 0x45, 0x0, 0x21},
+				},
+			},
+			wantLinkID: 0x210045c1,
+			wantFound:  true,
+		},
+		{
+			name: "Multiple TLVs",
+			tlvs: []proxyproto.TLV{
+				{ // AWS
+					Type:   0xEA,
+					Length: 12,
+					Value:  []byte{0x01, 0x76, 0x70, 0x63, 0x65, 0x2d, 0x61, 0x62, 0x63, 0x31, 0x32, 0x33},
+				},
+				{ // Azure but wrong subtype
+					Type:   0xEE,
+					Length: 5,
+					Value:  []byte{0x02, 0x01, 0x01, 0x01, 0x01},
+				},
+				{ // Azure but wrong length
+					Type:   0xEE,
+					Length: 3,
+					Value:  []byte{0x02, 0x01, 0x01},
+				},
+				{ // Correct
+					Type:   0xEE,
+					Length: 5,
+					Value:  []byte{0x1, 0xc1, 0x45, 0x0, 0x21},
+				},
+				{ // Also correct, but second in line
+					Type:   0xEE,
+					Length: 5,
+					Value:  []byte{0x1, 0xc1, 0x45, 0x0, 0x22},
+				},
+			},
+			wantLinkID: 0x210045c1,
+			wantFound:  true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			gotLinkID, gotFound := FindAzurePrivateEndpointLinkID(tt.tlvs)
+			if gotFound != tt.wantFound {
+				t.Errorf("FindAzurePrivateEndpointLinkID() got1 = %v, want %v", gotFound, tt.wantFound)
+			}
+			if gotLinkID != tt.wantLinkID {
+				t.Errorf("FindAzurePrivateEndpointLinkID() got = %v, want %v", gotLinkID, tt.wantLinkID)
+			}
+		})
+	}
+}