Features | Pricing | Documentation | Contact | Blog | About

Custom::ProxylityUdpGatewayPacketSource

Registers an SNS topic in your AWS account as a source of server-initiated UDP packets for a Listener. Once created, your application publishes messages to the topic and Proxylity delivers the corresponding UDP packets to the specified remote clients — without requiring any inbound request to trigger the delivery.

See Packet Sources for a conceptual overview and per-listener-type usage guidance.

Syntax

To declare this entity in your AWS CloudFormation template, use the following syntax:

JSON

{
  "Type" : "Custom::ProxylityUdpGatewayPacketSource",
  "Properties" : {
    "ServiceToken" : String,
    "ApiKey" : String,
    "ListenerName" : String,
    "SnsTopicSource" : {
      "TopicArn" : String,
      "Role" : String
    },
    "Description" : String
  }
}

YAML

Type: Custom::ProxylityUdpGatewayPacketSource
Properties:
  ServiceToken: String
  ApiKey: String
  ListenerName: String
  SnsTopicSource:
    TopicArn: String
    Role: String
  Description: String

Properties

ServiceToken

The ARN of the Proxylity custom resource handler in the deployment region, found in the customer configuration provided by Proxylity. See AWS::CloudFormation::CustomResource.

Required: Yes

Type: String

Update requires: Replacement

ApiKey

The API key provided to you by Proxylity for your account.

Required: Yes

Type: String

Update requires: No interruption

ListenerName

The name of the Custom::ProxylityUdpGatewayListener to bind. Proxylity uses this Listener as the egress endpoint for packets published to the SNS topic. The Listener must exist in the same AWS account and region as the stack.

Required: Yes

Type: String

Update requires: Replacement

SnsTopicSource

Identifies the SNS topic that will supply packets and the IAM role Proxylity must assume to subscribe to it.

Required: Yes

Type: Object

Update requires: No interruption

SnsTopicSource.TopicArn

The ARN of the SNS topic in your account. The topic must be in the same region as the CloudFormation stack. Proxylity subscribes its delivery queues to this topic using the role specified in SnsTopicSource.Role, so messages published to the topic are automatically delivered to the Listener's egress path.

Required: Yes

Type: String (ARN)

Update requires: No interruption

SnsTopicSource.Role

The ARN of the IAM role that Proxylity assumes to subscribe its SQS delivery queues to your SNS topic. The role must grant the following permissions on the topic resource:

See Connect Your AWS Account for guidance on setting up cross-account role trust relationships with Proxylity.

Required: Yes

Type: String (ARN)

Update requires: No interruption

Description

A short human-readable description of this Packet Source for your reference.

Required: No

Type: String

Update requires: No interruption

Return Values

Ref

When you pass the logical ID of this resource to the intrinsic Ref function, Ref returns the internal identifier assigned to the Packet Source.

SNS Message Format

Each message published to the SNS topic must contain a JSON object with a Messages array. Each element of the array represents one UDP packet to deliver.

{
  "Messages": [
    {
      "Data": "String",
      "Formatter": "String",
      "Tag": "String",
      "Remote": {
        "Address": "String",
        "Port": Number,
        "PeerKey": "String"
      },
      "Inner": {
        "SourceAddress": "String",
        "SourcePort": Number,
        "DestinationAddress": "String",
        "DestinationPort": Number,
        "Version": Number,
        "Protocol": Number
      }
    }
  ]
}

In addition to the message body, you must set the EgressRegion SNS message attribute to the AWS region where the packet should exit the Proxylity network. Proxylity subscribes one SQS delivery queue per supported region, each filtered on this attribute. Messages published without a matching EgressRegion attribute will not be delivered to any queue and will be silently discarded.

MessageAttributes={
    "EgressRegion": {
        "DataType": "String",
        "StringValue": "us-east-1"  # the region where the packet should egress
    }
}

A single SNS publish can include multiple packets in the Messages array. All packets in the array are processed independently; a delivery failure for one packet does not affect the others.

Messages[*].Data

The UDP payload to send. The encoding is determined by Formatter.

Required: Yes

Type: String

Messages[*].Formatter

The encoding used for Data. Supported values: base64 (default), hex, utf8, ascii, coap. When omitted, base64 is assumed.

Required: No

Type: String (enum)

Default: base64

Messages[*].Tag

An optional correlation identifier. When provided, this value is recorded in delivery telemetry and can be used to correlate a sourced packet with a prior inbound request.

Required: No

Type: String

Messages[*].Remote

Identifies the UDP client that should receive the packet.

Required: Yes

Remote.Address

The IPv4 or IPv6 address of the destination client.

Required: Yes

Type: String

Remote.Port

The UDP port number of the destination client.

Required: Yes

Type: Number

Remote.PeerKey

The base64-encoded WireGuard public key of the target peer. Required when the bound Listener is a WireGuard listener; Proxylity uses this key to select the correct WireGuard session and encrypt the packet before delivery. Ignored for plain UDP listeners.

Required: Yes (WireGuard listeners only)

Type: String (base64-encoded 32 bytes)

Messages[*].Inner

Provides inner IP/UDP addressing for WireGuard Listeners that have DecapsulatedDelivery enabled. When DecapsulatedDelivery is true, Proxylity re-encapsulates the payload into an inner IP/UDP frame using these addresses before encrypting and sending via WireGuard. This allows the WireGuard peer to see a packet that appears to originate from an inner network address rather than from the gateway itself.

The Inner block is required for WireGuard Listeners with DecapsulatedDelivery enabled, and ignored for all other listeners.

Inner.SourceAddress

The source IP address to set in the inner IP header.

Required: Yes (when Inner is present)

Type: String

Inner.SourcePort

The source UDP port to set in the inner UDP header.

Required: Yes (when Inner is present)

Type: Number

Inner.DestinationAddress

The destination IP address to set in the inner IP header.

Required: Yes (when Inner is present)

Type: String

Inner.DestinationPort

The destination UDP port to set in the inner UDP header.

Required: Yes (when Inner is present)

Type: Number

Inner.Version

The IP version for the inner packet (4 or 6). When omitted, the version is inferred from the address family of Inner.SourceAddress.

Required: No

Type: Number

Inner.Protocol

The IP protocol number for the inner packet. When omitted, defaults to 17 (UDP).

Required: No

Type: Number

Default: 17

Examples

EgressRegion

An SNS message attribute (not part of the message body) that identifies the AWS region through which Proxylity should send the packet. Proxylity maintains one regional delivery queue per supported region, each with an SNS filter policy matching this attribute value. Set this to the region closest to your target client, or to the region where the client's WireGuard session was originally established.

For replies to inbound packets, use the ingress region recorded in the original packet JSON envelope — this ensures the reply egresses from the same region the client's traffic entered, which is required for WireGuard session compatibility.

Required: Yes

Type: SNS message attribute (String)

Examples

Packet Source for a Plain UDP Listener

This example creates a Packet Source binding an SNS topic to a UDP Listener. The application publishes to the SNS topic to send packets to remote clients.

YAML

ReplyTopic:
  Type: AWS::SNS::Topic

PacketSourceRole:
  Type: AWS::IAM::Role
  Properties:
    AssumeRolePolicyDocument:
      Version: "2012-10-17"
      Statement:
        - Effect: Allow
          Principal:
            AWS: !Sub "arn:aws:iam::${ProxylityAccountId}:root"
          Action: sts:AssumeRole
    Policies:
      - PolicyName: AllowSnsSubscription
        PolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Action:
                - sns:Subscribe
                - sns:Unsubscribe
                - sns:ListSubscriptionsByTopic
              Resource: !Ref ReplyTopic

MyPacketSource:
  Type: Custom::ProxylityUdpGatewayPacketSource
  Properties:
    ServiceToken: !Ref ProxylityServiceToken
    ListenerName: !GetAtt MyListener.Name
    SnsTopicSource:
      TopicArn: !Ref ReplyTopic
      Role: !GetAtt PacketSourceRole.Arn
    Description: "Sourced replies for MyListener"

Publishing a packet from a Lambda function (Python)

import boto3, json, base64

sns = boto3.client("sns")
payload = b"\x01\x02\x03\x04"  # your UDP payload

sns.publish(
    TopicArn="arn:aws:sns:us-east-1:123456789012:ReplyTopic",
    MessageAttributes={
        "EgressRegion": {"DataType": "String", "StringValue": "us-east-1"}
    },
    Message=json.dumps({
        "Messages": [
            {
                "Data": base64.b64encode(payload).decode(),
                "Remote": {
                    "Address": "203.0.113.42",
                    "Port": 4567
                }
            }
        ]
    })
)

Packet Source for a WireGuard Listener

WireGuard Listeners require the PeerKey field so Proxylity can select the correct encrypted session. If the Listener has DecapsulatedDelivery enabled, each packet must also include an Inner block with the inner IP addressing.

Publishing to a standard WireGuard Listener (C# / .NET)

This pattern is taken from the Raptor open-source project, which uses a Packet Source to send RaptorQ block-completion acknowledgements back to a WireGuard-connected encoder client.

var reply = new ProxylityResponse(Messages: [
    new ProxylityReplyMessage(
        Remote: new ProxylityEndpoint(
            Address: remoteEndpoint.Address.ToString(),
            Port: remoteEndpoint.Port,
            PeerKey: peerKey),
        Data: Convert.ToBase64String(payload)
    )
]);
await sns.PublishAsync(new PublishRequest
{
    TopicArn = REPLY_TOPIC_ARN,
    MessageAttributes = new()
    {
        ["EgressRegion"] = new MessageAttributeValue
        {
            DataType = "String",
            StringValue = egressRegion   // ingress region from the original inbound packet
        }
    },
    Message = JsonSerializer.Serialize(reply)
}, token);

Publishing to a WireGuard Listener with DecapsulatedDelivery (Python)

sns.publish(
    TopicArn="arn:aws:sns:us-east-1:123456789012:WireGuardReplyTopic",
    MessageAttributes={
        "EgressRegion": {"DataType": "String", "StringValue": "us-east-1"}
    },
    Message=json.dumps({
        "Messages": [
            {
                "Data": base64.b64encode(payload).decode(),
                "Remote": {
                    "Address": "203.0.113.42",
                    "Port": 51820,
                    "PeerKey": "base64encodedWireGuardPublicKey=="
                },
                "Inner": {
                    "SourceAddress": "10.0.0.1",
                    "SourcePort": 53,
                    "DestinationAddress": "10.0.0.2",
                    "DestinationPort": 12345
                }
            }
        ]
    })
)

Multiple Packets in One Publish

A single SNS publish can fan out to multiple clients by including several entries in the Messages array. All packets in a single publish must target the same EgressRegion.

sns.publish(
    TopicArn="...",
    MessageAttributes={
        "EgressRegion": {"DataType": "String", "StringValue": "us-east-1"}
    },
    Message=json.dumps({
        "Messages": [
            {
                "Data": base64.b64encode(payload).decode(),
                "Remote": { "Address": "203.0.113.1", "Port": 4567 }
            },
            {
                "Data": base64.b64encode(payload).decode(),
                "Remote": { "Address": "203.0.113.2", "Port": 4567 }
            }
        ]
    })
)

Error Handling

Packet Sources inherit UDP semantics: delivery is best-effort and unacknowledged. If Proxylity cannot deliver a sourced packet (the remote address is unreachable, the Remote object is malformed, or other errors), the packet is silently discarded.

To capture undeliverable messages, attach a dead-letter queue to your SNS topic. Structured delivery error events are also emitted to the Listener's CloudWatch log stream alongside inbound packet events.

Billing

Packets delivered via a Packet Source are billed at the same per-packet rate as inbound packets, consistent with your AWS Marketplace subscription tier.