UDP Gateway Packet Sources

mlhpdx1 pts0 comments

Now Available: Packet Sources - Proxylity Blog

Now Available: Packet Sources

By Lee Harding | June 8, 2026 | 6 min read

Packet Sources are now generally available to all customers with an AWS Marketplace subscription.<br>With a Packet Source, your application can send UDP packets to connected clients at any time —<br>without waiting for those clients to send a request first. Push notifications, game state updates,<br>over-the-air firmware delivery, async acknowledgements: they're all straightforward to build now,<br>on top of the same Listeners you already use for inbound traffic.

We announced the feature in Select Availability<br>back in April. This post covers what's changed since then and what you need to know to<br>get started.

What Changed Since Select Availability

The design described in the original article — an SNS topic in your account bound to a Listener via<br>a CloudFormation resource — is the same. But several things have been refined or added since April.

Message format

Each SNS message body must now wrap packets in a Messages array. This unlocks fan-out:<br>a single SNS publish can deliver to multiple clients in one operation, which is useful for<br>broadcasting game state or sensor commands to a group of peers.

"Messages": [<br>"Data": "AQIDBA==",<br>"Remote": { "Address": "203.0.113.42", "Port": 4567 }

The Remote object uses the field name Address (not IP, as shown<br>in the earlier article). The rest of the structure is unchanged.

EgressRegion — required message attribute

This is the most important operational detail. Proxylity subscribes one SQS delivery queue per<br>supported AWS region to your SNS topic, each with a filter policy keyed on the<br>EgressRegion SNS message attribute. You must set this attribute on every publish;<br>without it the message matches no filter and is silently discarded — no packet is delivered.

Set EgressRegion to the AWS region where you want the packet to exit the network.<br>For replies to inbound packets, use the IngressRegion value from the original packet<br>JSON — this ensures the reply takes the same regional path the client's traffic entered, which is<br>required for WireGuard session compatibility.

import boto3, json, base64

sns = boto3.client("sns")<br>payload = b"\x01\x02\x03\x04"

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

All formatters are supported

The select availability release only supported base64 encoding. The GA release supports<br>all the same formatters available to Destinations: base64, hex,<br>utf8, ascii, and coap. Specify the optional<br>Formatter field in each message entry; it defaults to base64 when<br>omitted.

WireGuard Support

Packet Sources work with both plain UDP Listeners and WireGuard Listeners. For WireGuard you set<br>Remote.PeerKey to the base64-encoded public key of the target peer; the Gateway looks<br>up the active WireGuard session for that key and encrypts the packet before delivery. If no session<br>exists for the key, the packet is dropped.

If your WireGuard Listener has DecapsulatedDelivery enabled, sourced packets must<br>also include an Inner block with inner IP/UDP addressing so the Gateway can<br>re-encapsulate the payload correctly before sending it through the tunnel:

"Messages": [<br>"Data": "AQIDBA==",<br>"Remote": {<br>"Address": "203.0.113.42",<br>"Port": 51820,<br>"PeerKey": "base64encodedWireGuardPublicKey=="<br>},<br>"Inner": {<br>"SourceAddress": "10.0.0.1",<br>"SourcePort": 53,<br>"DestinationAddress": "10.0.0.2",<br>"DestinationPort": 12345

Setting Up a Packet Source

The CloudFormation resource is Custom::ProxylityUdpGatewayPacketSource. It binds an<br>SNS topic you own to a named Listener. Proxylity assumes the role you provide to subscribe its<br>delivery queues to the topic; those subscriptions are removed automatically when you delete the<br>resource.

The IAM policy for the role must be attached before CloudFormation attempts to create the Packet<br>Source, so use DependsOn to enforce the ordering:

ReplyTopic:<br>Type: AWS::SNS::Topic

ProxylitySubscribeToTopicPolicy:<br>Type: AWS::IAM::Policy<br>Properties:<br>PolicyName: ProxylitySubscribeToTopicPolicy<br>PolicyDocument:<br>Version: "2012-10-17"<br>Statement:<br>- Effect: Allow<br>Action:<br>- sns:Subscribe<br>- sns:Unsubscribe<br>- sns:ListSubscriptionsByTopic<br>Resource: !GetAtt ReplyTopic.TopicArn<br>Roles:<br>- !Ref RoleForProxylity

ReplySource:<br>DependsOn:<br>- ProxylitySubscribeToTopicPolicy<br>Type: Custom::ProxylityUdpGatewayPacketSource<br>Properties:<br>ServiceToken: !FindInMap [ProxylityConfig, !Ref "AWS::Region", ServiceToken]<br>ApiKey: !FindInMap [ProxylityConfig, Account, ApiKey]<br>ListenerName: !Ref MyListener<br>SnsTopicSource:<br>TopicArn: !GetAtt ReplyTopic.TopicArn<br>Role: !GetAtt RoleForProxylity.Arn

Example: CoAP Observe with a WireGuard Listener

The best place to start is the<br>CoAP Time Service<br>in the Proxylity examples repository. It deploys a fully working CoAP server...

packet message wireguard base64 sources topic

Related Articles