Skip to content

Commit

Permalink
Allow sending data to a specific IP address instead of the whole LAN
Browse files Browse the repository at this point in the history
  • Loading branch information
k-yle committed Jul 22, 2023
1 parent 073d282 commit 4ababfd
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 10 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:

strategy:
matrix:
node-version: [10.x, 12.x, 14.x, 16.x, 18.x]
node-version: [10.x, 12.x, 14.x, 16.x, 18.x, 20.x]

steps:
- name: ⏬ Checkout code
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [4.4.0] - 2023-07-22

### Added

- [#52]: Allow sending data to a specific IP address instead of the whole LAN

## [4.3.0] - 2023-02-10

### Added
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sacn",
"version": "4.3.0",
"version": "4.4.0",
"description": "💡 🎭 Send and Receive sACN data (DMX over IP)",
"author": "Kyle Hensel",
"license": "Apache-2.0",
Expand Down
10 changes: 9 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ main(); // wrapped in a main() function so that we can `await` the promise
| `reuseAddr` | `boolean` | Optional. Allow multiple programs on your computer to send to the same sACN universe. |
| `defaultPacketOptions` | `object` | Optional. You can specify options like `sourceName`, `cid`, and `priority` here instead of on every packet |
| `iface` | `string` | Optional. Specifies the IPv4 address of the network interface/card to use. | OS default interface (=active internet connection)
| `useUnicastDestination`| `string` | Optional. Setting this attribute to an IPv4 address will cause data to be sent directly to that device, instead of broadcasting to the whole LAN. |

# Contribute

Expand All @@ -135,7 +136,7 @@ npm test # run tests

# Network Requirements

- [x] Multicast must be enabled. sACN uses port `5568` on `239.255.x.x`
- [x] Multicast must be enabled<sup id="footnote-source1">[1](#footnote1)</sup>. sACN uses port `5568` on `239.255.x.x`
- [x] Network infrastructure that supports at least 100Mbps (100BaseT)

# Protocol Docs
Expand All @@ -144,3 +145,10 @@ The Architecture for Control Networks (ACN) and derived protocols are created by

- sACN is defined in [ANSI E1.31](./docs/E1.31-2018.pdf)
- RDMNet is defined in [ANSI E1.33](./docs/E1.33-2019.pdf)

---
<small id="footnote1">

1&shy;. Unicast is also supported by default, but this is not how sACN normally works. See [_Table 3_](#table-3---options-for-sender) for how to send data directly to a unicast address. [](#footnote-source1)

</small>
19 changes: 16 additions & 3 deletions src/sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ interface Props {

// IPv4 address of the network interface
iface?: string;

/**
* If you set this option to an IP address, then data will be sent
* purely to this address, instead of the whole network.
*
* This option is not recommended and may not be supported by all devices.
*/
useUnicastDestination?: string;
}

export class Sender {
Expand All @@ -29,7 +37,11 @@ export class Sender {

public readonly universe: Props['universe'];

private readonly multicastDest: string;
/**
* this is normally a multicast address, but it could be
* a unicast address if the user configures `useUnicastDestination`
*/
readonly #destinationIp: string;

private readonly defaultPacketOptions: Props['defaultPacketOptions'];

Expand All @@ -51,10 +63,11 @@ export class Sender {
minRefreshRate = 0,
defaultPacketOptions,
iface,
useUnicastDestination,
}: Props) {
this.port = port;
this.universe = universe;
this.multicastDest = multicastGroup(universe);
this.#destinationIp = useUnicastDestination || multicastGroup(universe);
this.defaultPacketOptions = defaultPacketOptions;

this.socket = createSocket({ type: 'udp4', reuseAddr });
Expand Down Expand Up @@ -84,7 +97,7 @@ export class Sender {
sequence: this.sequence,
});
this.sequence = (this.sequence + 1) % 256;
this.socket.send(buffer, this.port, this.multicastDest, (err) =>
this.socket.send(buffer, this.port, this.#destinationIp, (err) =>
err ? reject(err) : resolve(),
);
});
Expand Down
57 changes: 55 additions & 2 deletions test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,6 @@ describe('Sending to a specific interface', () => {
await Tx2.send({ payload: { 22: 0xff } });
await sleep(3500);

console.error(errors, received1.length, received2.length);

assert.strictEqual(errors.length, 0);
assert.strictEqual(received1.length, 1);
assert.strictEqual(received2.length, 1);
Expand All @@ -301,3 +299,58 @@ describe('Sending to a specific interface', () => {
}
});
});

describe('Sending to a unicast address', () => {
it('can send data to a specific unicast address', async () => {
const Tx = new Sender({
universe: 51,
useUnicastDestination: '127.0.0.1',
});
const Rx = new Receiver({ universes: [51] });
try {
const received: Packet[] = [];
const errors: Error[] = [];
Rx.on('packet', (packet) => received.push(packet));

collectErrors(Rx, errors);

// stuff takes time
await sleep(3500);
await Tx.send({ payload: { 22: 0xff } });
await sleep(3500);

assert.strictEqual(errors.length, 0);
assert.strictEqual(received.length, 1);
assert.deepStrictEqual(received[0]!.payload, { 22: 100 });
} finally {
Tx.close();
Rx.close();
}
});

it('receives nothing when sending data to an invalid unicast address', async () => {
const Tx = new Sender({
universe: 51,
useUnicastDestination: '192.0.2.0', // https://en.wikipedia.org/wiki/Reserved_IP_addresses
});
const Rx = new Receiver({ universes: [51] });
try {
const received: Packet[] = [];
const errors: Error[] = [];
Rx.on('packet', (packet) => received.push(packet));

collectErrors(Rx, errors);

// stuff takes time
await sleep(3500);
await Tx.send({ payload: { 22: 0xff } });
await sleep(3500);

assert.strictEqual(errors.length, 0);
assert.strictEqual(received.length, 0); // nothing was recevied
} finally {
Tx.close();
Rx.close();
}
});
});

0 comments on commit 4ababfd

Please sign in to comment.