Unable to connect to GPSD on port 2947 from Bridge mode, but able to from host mode. Why?

I have been talking with one of the developers of SignalK, and he was unable to understand this. I have SignalK boating software running on Docker in Pi 192.168.3.1. Then I have GPSD with a USB GPS mouse on a Pi with the address 192.168.3.2. When I use Bridge mode (which I much prefer, because then I can use port 80 for the SignalK GUI, that does not work in host mode), I can not connect to GPSD. No data comes in, and the GUI says “Connecting” forever. When I use Host mode, it connects at once and all GPS data comes in. This is the Docker Compose file I use for Bridge mode:

services:
  signalk-server:
    image: cr.signalk.io/signalk/signalk-server:latest
    container_name: SignalK
    restart: no
    ports:
      - 80:80
      - 1884:1884
    network_mode: bridge    # (1/3) If bridge-mode is used, then comment line abobe (host) and select/add needed ports settings

    devices:
      - /dev/ttyUSB0:/dev/ttyUSB0 #Sailor Hat
      - /dev/ttyUSB1:/dev/ttyUSB1 #Garmin
    volumes:
      - /dev:/dev
      - /home/pi/Docker-Compose/SignalK/SignalK-data:/home/node/.signalk
    entrypoint: sh /home/node/signalk/startup.sh
    privileged: true
    logging:
      options:
        max-size: 10m

Is there anything in there that should create any problems? The developer asked me to launch the server like this:

docker run -it --rm --entrypoint /usr/bin/node  signalk/signalk-server

And then paste this code:

const socket = require('net').connect(2947, '192.168.3.2', function () {
  console.log('Connected')
  socket.write("R\r\n")

  socket.on("data", function (data) {
    console.log("received data")
    console.log(data.toString())
    socket.end()
  })
})
socket.on("error", function (err) {
  console.log('Error', err)
})

That errored out:

<ref *1> Error: connect ECONNREFUSED 192.168.3.2:2947
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1606:16)
    at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
  errno: -111,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '192.168.3.2',
  port: 2947,
  domainEmitter: Socket {
    connecting: false,
    _hadError: false,
    _parent: null,
    _host: null,
    _closeAfterHandlingError: false,
    _events: {
      close: undefined,
      error: undefined,
      prefinish: undefined,
      finish: undefined,
      drain: undefined,
      data: undefined,
      end: [Function: onReadableStreamEnd],
      readable: undefined,
      connect: [Function]
    },
    _readableState: ReadableState {
      highWaterMark: 16384,
      buffer: [],
      bufferIndex: 0,
      length: 0,
      pipes: [],
      awaitDrainWriters: null,
      [Symbol(kState)]: 1053110,
      [Symbol(kErroredValue)]: [Circular *1]
    },
    _writableState: WritableState {
      highWaterMark: 16384,
      length: 0,
      corked: 0,
      onwrite: [Function: bound onwrite],
      writelen: 0,
      bufferedIndex: 0,
      pendingcb: 0,
      [Symbol(kState)]: 17564598,
      [Symbol(kBufferedValue)]: null,
      [Symbol(kErroredValue)]: [Circular *1]
    },
    allowHalfOpen: false,
    _maxListeners: undefined,
    _eventsCount: 2,
    _sockname: null,
    _pendingData: null,
    _pendingEncoding: '',
    server: null,
    _server: null,
    [Symbol(async_id_symbol)]: 31,
    [Symbol(kHandle)]: null,
    [Symbol(lastWriteQueueSize)]: 0,
    [Symbol(timeout)]: null,
    [Symbol(kBuffer)]: null,
    [Symbol(kBufferCb)]: null,
    [Symbol(kBufferGen)]: null,
    [Symbol(shapeMode)]: true,
    [Symbol(kCapture)]: false,
    [Symbol(kSetNoDelay)]: false,
    [Symbol(kSetKeepAlive)]: false,
    [Symbol(kSetKeepAliveInitialDelay)]: 0,
    [Symbol(kBytesRead)]: 0,
    [Symbol(kBytesWritten)]: 0
  },
  domainThrown: false

This way of starting the image:

docker run -it --rm --entrypoint /usr/bin/node --network host signalk/signalk-server

and pasting the same code, did not:

Welcome to Node.js v20.15.0.
Type ".help" for more information.
> const socket = require('net').connect(2947, '192.168.3.2', function () {
...   console.log('Connected')
...   socket.write("R\r\n")
... 
...   socket.on("data", function (data) {
...     console.log("received data")
...     console.log(data.toString())
...     socket.end()
...   })
... })
undefined
> socket.on("error", function (err) {
...   console.log('Error', err)
... })Connected
received data
{"class":"VERSION","release":"3.22","rev":"3.22","proto_major":3,"proto_minor":14}

He ran out of ideas on that. I have no clue. Maybe some of the knowledgeable people here can help?

Nobody has an informed idea about this? I tried to install ping and run that from an exec command, and I could ping 192.168.3.2 from host mode, but not from bridge mode. Is that normal?

What is Pi 192.168.3.1? Is that the host IP?

How does the device get a different IP? Is there a DHCP router involved?

How is the network 192.168.3.1 configured? What subnet mask is set?

I would think the subnet mask is wrong and/or you need to enable routing.

Just to be sure: you use the ip of the docker host, and try to access a container by a port that you didn’t publish? Of course, it would make sense in network_mode: host, as the container will directly bind the host ports. Seems like you really just forgot to publish the port? Note: broadcast or multicast network messages will not cross the boundaries of a bridge network.

One more thing: you don’t want to use network_mode: bridge, as it will force the container to use the (legacy) default bridge network bound to the docker0 interface, instead of using the user defined bridge network docker compose creates for your compose project anyway.

Sorry, I probably didn’t explain it well enough. The basics:

Pi number one is a Pi 4 set up as 192.168.3.1, to be a router, the network mask is 255.255.255.0. The routing is to get from the boat’s 192.168.3.x network to my cabin’s 192.168.2.x network. Devices inside this segment can get to the external cabin network, but devices on the cabin network can only get to Pi 1, because there is no NAT set up on Pi 1, I really don’t need that. Routing is set up in Network Manager, with wifi in (Asus 5gHz USB dongle in a box on the roof of the boat to the closest AP on my Asus AI Mesh in the cabin) and LAN out from Pi 192.168.3.1 to Pi 192.168.3.2. The SignalK docker image is running on this Pi.

Pi number two 192.168.3.2 is running as an access point for phones, a tablet and a bunch of ESP32’s in the boat. GPSD is running on this Pi, on port 2947.

So Pi 1 with the SignalK docker image and Pi 2 with GPSD are on the same segment, connected by a LAN cable. As far as I know there is no firewall active anywhere, blocking anything. At least I haven’t installed one. In the cabin I have a pfSense firewall that should stop anything, so I don’t see the point in firewalling the boat.

SignalK docker image in bridge mode, published ports: 80 for GUI and 1884 for a separate MQTT broker in the image, both working and accessable from outside. And the problem, 2947 for GPSD. I doubt that I need to publish that because this is SignalK in the docker imagetrying to access GPSD outside the docker image. But GPSD does not work without publishing 2947 either.

SignalK docker image in host mode: No ports published. 1884 for the separate MQTT in the image is working. 2947 for GPSD from SignalK to GPSD on Pi 2 is working. 80 for GUI is not working, according to one of the SignalK developers because the SignalK docker image is using user node that is not privileged and can not bind to ports below 1024. The standard GUI port is 3000.

So the problem is that with the SignalK docker image in bridge mode I can’t get to GPSD, and with the SignalK docker image in host mode I can’t use 80 for the GUI, which I would very much like to do.

In bridge mode I can’t ping Pi 2 or any of the ESP32’s from the SignalK docker image. The ESPs are on 192.168.3.100 and up, ping works in host mode. This is the message I get on Ping:

From 172.17.0.1 icmp_seq=1 Destination Port Unreachable

So I guess that means that the address of the default/legacy bridge is 127.17.0.x, right? So wrong segment, and no routing to 192.168.3.x.

@meyay If that can help me out, how should I set up the network mode instead of the legacy brige? I think I probably understand the problem now, because the bridge has scope local when I use docker network ls I have tried to read around, but the articles I have found are about letting Docker images connect to each other, not another computer on the same network segment.

This used to be true, and is still true when your system uses a Linux Kernel < 4.11. For Kernel 4.11 and later docker takes care of lifting that restriction.

See: Non-root user able to bind to port 80; why? - #6 by meyay

Small note: images are the blueprints that are stored local, in registries or transferred on the wire when pulling/pushing an image. The runtime instance is a container.

If GPSD listens on port 2947/tcp or 2947/udp, it shouldn’t matter if the SignalK container is attached to the host network, or a bridge network. Since traffic coming from a bridge network is source natted (container ip replaced with host ip), it will appear like the traffic was send from the host directly.

1 Like

I stand corrected, the SignalK container is running on Pi 1.

And I really wonder why it isn’t working. Both GPSD on 2947 in Bridge mode and port 80 in host mode should really work, as far as I understand this is a much newer kernel than 4.4:

$ uname -srm
Linux 6.6.31+rpt-rpi-v8 aarch64

Isn’t it strange that I can’t even ping anything on the same segment except for the Docker host? How would you recommend that I set up the network?