Accessing ROS master from outside the container

I have a resin device that is running a ROS master.

I can run ros nodes, but it is hard to monitor the topics without the visualization tools such as rviz, rqt .etc

I would like to connect to the ROS master from outside the container in order to run GUI applications.

I have tried using public_url:11311 as ROS_MASTER_URI on my laptop, while running roscore in the container, but I cannot connect to the 11311 port

How can I access ROS master inside the container?

Hi @jalim,

I’m not familiar with how ROS works - could you explain a bit more what you’d do / how you’d do it if this were done without resin? Does the ROS master expose an API or something like that that you want to connect to?

@pcarranzav This explains how ROS master works.

If using a companion computer(Attached onboard the drone) a drone, usual use cases are as the following.

  • Access ROS master running inside the companion computer
  • Run multiple ROS masters for the ground control computer and companion computer and sync with node_manager

I am currently interested in the first use case, by connecting to the ROS master(usually at port 11311) in the container.

By exposing the ROS master to a public URL, I was hoping to connect to the master remotely. I tried setting the ROS master at port :80 or :8080 and enabling a public URL. However, when I run rostopic list in my laptop, I get the following error message.

ERROR: Unable to communicate with master!

@jalim are you using a single-container app or multi-container (i.e. do you have a docker-compose.yml)?

That will affect what you have to do to expose a port. Could you share relevant parts of your Dockerfile and your docker-compose.yml if you have one?

Also, you can try connecting to it through a local network first, instead of the public url. That will help check if the problem is with exposing the port or with something in how public urls work. i.e. connect to the same network and use <device IP>:11311 instead of the device url.

@pcarranzav I am using a single container app,

I have tried connecting to <device_IP>:11311 but was still not able to connect to the ROS master from my laptop

$ sudo resin local scan
Reporting scan results
- 
  host:          12f197a.local
  address:       192.168.1.116
  dockerInfo: 
    Containers:        2
    ContainersRunning: 2
    ContainersPaused:  0
    ContainersStopped: 0
    Images:            2
    Driver:            aufs
    SystemTime:        2018-07-03T17:18:11.351120958Z
    KernelVersion:     4.12.12-yocto-standard
    OperatingSystem:   Resin OS 2.13.1+rev1
    Architecture:      x86_64
  dockerVersion: 
    Version:    17.06.0-dev
    ApiVersion: 1.31
$ export ROS_MASTER_URI=http://192.168.1.116:11311
$ rostopic list
ERROR: Unable to communicate with master!

From the container, running ros core looks as below

# export ROS_HOSTNAME=localhost
# export ROS_MASTER_URI=http://localhost:11311
# roscore
... logging to /root/.ros/log/06816b82-7ee5-11e8-a2a2-f8633ff9a90c/roslaunch-12f197a-4385.log
Checking log directory for disk usage. This may take awhile.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

started roslaunch server http://localhost:39189/
ros_comm version 1.12.13


SUMMARY
========

PARAMETERS
 * /rosdistro: kinetic
 * /rosversion: 1.12.13

NODES

auto-starting new master
process[master]: started with pid [4395]
ROS_MASTER_URI=http://localhost:11311/

setting /run_id to 06816b82-7ee5-11e8-a2a2-f8633ff9a90c
process[rosout-1]: started with pid [4408]
started core service [/rosout]

From my understanding, for a single container app, all ports should be exposed, so I don’t understand why I cannot connect to the port directly.

I can run the same commands I ran in the container in a AWS server, and connect without any issues to the ROS master.

Hi!

We were wondering how the ROS master is started? Is there any configuration to set its own IP and port?

local network

One guess we have, is that if the service is started to listen on localhost/127.0.0.1, that won’t be available outside of the device (only within the device). To listen on all interfaces and thus be available outside of the machine, usually either the IP of an interface, or 0.0.0.0 needs to be set, this latter meaning “listen on all interfaces”. This might be a promising avenue.

public device url

Regarding the device’s Public URL, that will only work with a single port that is forwarded through our services.

Not sure what protocol the ROS master uses, but looks like a simple HTTP interface? Thus if ROS master can run on port 80, then the Public Device URL might be used to connect to the device:

  • run the service you want to expose on port 80, can be local to that device (so localhost is good). Only that port is forwarded, custom ports cannot be used.
  • that port will be forwarded through our VPN, and thus other devices can connect to the device through the Public Device URL

What do you think?

Some other things we thought that you can test:

Using curl to query the port on the host OS, which would clarify, whether the port is exposed properly outside of the container at least. (so could do a curl localhost:PORT in the host OS)

Another thing that we thought you might try is directly using curl to query the endpoint where the ROS master is expected to be. This could clear up the case if ROS_MASTER_URI is not honored for some reason, since it would show it clearer if anything’s available at that host:ip combination. So curl -v ip:port (-v to turn on verbosity).

@imrehg Thank you for the pointers,

ROS masters are always started on localhost, to start on :80, for example

$ roscore -p 80
... logging to /root/.ros/log/e1cbd866-7f90-11e8-8b82-f8633ff9a90c/roslaunch-12f197a-147.log
Checking log directory for disk usage. This may take awhile.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.

started roslaunch server http://localhost:39577/
ros_comm version 1.12.13


SUMMARY
========

PARAMETERS
 * /rosdistro: kinetic
 * /rosversion: 1.12.13

NODES

auto-starting new master
process[master]: started with pid [157]
ROS_MASTER_URI=http://localhost:80/

setting /run_id to e1cbd866-7f90-11e8-8b82-f8633ff9a90c
process[rosout-1]: started with pid [170]
started core service [/rosout]

I have tried having a roscore on a remote server, but having a roscore on a single remote server is not considered a good practice.

When I try connecting to the ROS master, on the public URL, the following error occurs

$ export ROS_MASTER_URI=https://12f197adbe18ad4c43c23a317271b7eb.resindevice.io/:80
$ rostopic list
Traceback (most recent call last):
  File "/opt/ros/kinetic/bin/rostopic", line 35, in <module>
    rostopic.rostopicmain()
  File "/opt/ros/kinetic/lib/python2.7/dist-packages/rostopic/__init__.py", line 2099, in rostopicmain
    _rostopic_cmd_list(argv)
  File "/opt/ros/kinetic/lib/python2.7/dist-packages/rostopic/__init__.py", line 2039, in _rostopic_cmd_list
    exitval = _rostopic_list(topic, verbose=options.verbose, subscribers_only=options.subscribers, publishers_only=options.publishers, group_by_host=options.hostname) or 0
  File "/opt/ros/kinetic/lib/python2.7/dist-packages/rostopic/__init__.py", line 1205, in _rostopic_list
    state = master.getSystemState()
  File "/opt/ros/kinetic/lib/python2.7/dist-packages/rosgraph/masterapi.py", line 481, in getSystemState
    return self._succeed(self.handle.getSystemState(self.caller_id))
  File "/usr/lib/python2.7/xmlrpclib.py", line 1243, in __call__
    return self.__send(self.__name, args)
  File "/usr/lib/python2.7/xmlrpclib.py", line 1602, in __request
    verbose=self.__verbose
  File "/usr/lib/python2.7/xmlrpclib.py", line 1283, in request
    return self.single_request(host, handler, request_body, verbose)
  File "/usr/lib/python2.7/xmlrpclib.py", line 1331, in single_request
    response.msg,
xmlrpclib.ProtocolError: <ProtocolError for 12f197adbe18ad4c43c23a317271b7eb.resindevice.io/:80: 500 Internal Server Error>

In the internal network,

$ export ROS_MASTER_URI=http://192.168.1.116:80
$ rostopic list
ERROR: Unable to communicate with master!

As @imrehg has suggested, have tried doing curl -v ip:port within the container, but the connection is refused.

curl -v localhost:80
* Rebuilt URL to: localhost:80/
*   Trying ::1...
* connect to ::1 port 80 failed: Connection refused
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/7.47.0
> Accept: */*
> 
* HTTP 1.0, assume close after body
< HTTP/1.0 501 Unsupported method ('GET')
< Server: BaseHTTP/0.3 Python/2.7.12
< Date: Wed, 04 Jul 2018 13:55:28 GMT
< Connection: close
< Content-Type: text/html
< 
<head>
<title>Error response</title>
</head>
<body>
<h1>Error response</h1>
<p>Error code 501.
<p>Message: Unsupported method ('GET').
<p>Error code explanation: 501 = Server does not support this operation.
</body>
* Closing connection 0

Hi @jalim,

The curl command should be run in the host OS rather than the container, as that would prove whether the service is exposing the port correctly. From the Dashboard go to the ‘Terminal’ panel for the device and select ‘Host OS’ (the top option), and then curl -v localhost:80.

You should either get the same response as you did within the container (that ‘GET’ is not a suported method, which is fine as it at least shows there’s something listening on that port), or something like * connect to ::1 port 80 failed: Connection refused which would suggest that the container hasn’t exposed port 80 and therefore can’t be contacted.

If you could share the relevant parts of the Dockerfile you’re using, that would also be extremely helpful!

@hedss Sorry for the confusion, I have also tested it in the hostOS but have the same respose

# curl -v localhost:80
* Rebuilt URL to: localhost:80/
*   Trying ::1...
* TCP_NODELAY set
* connect to ::1 port 80 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/7.54.1
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 501 Unsupported method ('GET')
< Server: BaseHTTP/0.3 Python/2.7.12
< Date: Wed, 04 Jul 2018 14:30:02 GMT
< Connection: close
< Content-Type: text/html
<
<head>
<title>Error response</title>
</head>
<body>
<h1>Error response</h1>
<p>Error code 501.
<p>Message: Unsupported method ('GET').
<p>Error code explanation: 501 = Server does not support this operation.
</body>
* Closing connection 0

I am not sure whether there is a relevent part of the Dockerfile as I am simply installing ROS and I am using resin ssh to run roscore on the device

Hi @jalim,

No problem. :slightly_smiling_face: That’s actually really good news then, as it shows that the service container is correctly exposing the port externally. My next question would be does this also work on an external computer (ie. a laptop), by using the IP of the device, so curl -v http://<deviceIp>:80?

It looks like it’s attempting to start on localhost only, so I would expect the above curl from a separate host to fail. Unfortunately I’m unfamiliar with ROS, so I don’t know how this should work, but is there a way of configuring it to listen on all interfaces (ie. 0.0.0.0) instead of just localhost (127.0.0.1)?

@hedss I appreciate the fast response

In ROS, in order to run a node, the node needs to register it’s address to the ROS master. Therefore, the ros master(running roscore) should have a relevent address.

It fails if I assign 0.0.0.0:80 for the rosmaster as there is no roscore running on 0.0.0.0:80

The result of the curl from an external machine is as the following

$ curl -v http://192.168.1.116:80
* Rebuilt URL to: http://192.168.1.116:80/
*   Trying 192.168.1.116...
* connect to 192.168.1.116 port 80 failed: Connection refused
* Failed to connect to 192.168.1.116 port 80: Connection refused
* Closing connection 0
curl: (7) Failed to connect to 192.168.1.116 port 80: Connection refused

This is the relevent part of the Dockerfile

RUN apt-get update \
	&& apt-get install -y --no-install-recommends apt-utils \
	&& apt-get install -y lsb-release
# Add keys
RUN sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
RUN apt-key adv --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-key 421C365BD9FF1F717815A3895523BAEEB01FA116

# install common dependencies
RUN apt-get update \
    && apt-get install -y $(grep -vE "^\s*#" package.common  | tr "\n" " ") \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

and the list package.common contains the list of packages to install as the following

python-future
python-rosinstall
python-rosinstall-generator
python-wstool
python-catkin-tools
ros-kinetic-ros-base
ros-kinetic-mavros
ros-kinetic-mavros-extras

You can run

source "/opt/ros/kinetic/setup.bash"
export ROS_HOSTNAME=localhost
export ROS_MASTER_URI=http://localhost:11311
roscore -p 80

in the container in order to run roscore

Hi @jalim,

So it does fail how I’d expect. I’m not entirely sure I understand where the master is running, it’s running on the device, right? And it’s the master you’re trying to contact?

Looking at the documentation here: http://wiki.ros.org/ROS/Tutorials/UnderstandingNodes it suggests that ROS_MASTER_URL needs to be set to the relevant machine name:


ROS_MASTER_URI=http://machine_name:11311/

(Sorry, hit enter accidentally)

But, it’s being set to localhost in your case, which means it’ll never listen on an external interface (ie. you can’t connect to it if you’re not on the same device). It’s also using port 11311, so you’d need to EXPOSE 11311 in your Dockerfile.

I suspect what you actually want is the IP address of the device, ie:

ROS_MASTER_URI=http://192.168.1.116:11311/

or using port 80 if that’s been started as such.

This should then bind to the external interface instead of localhost. You might be able to use http://0.0.0.0:1131 to bind to all interfaces, depending on how the ROS code works.

@hedss
I think even the rosmaster is run by local host, in normal use cases it should also be accessible from the outside world. I can connect to my rosmaster running on my laptop from the container, by doing

export ROS_MASTER_URI=http://<IP of laptop>:80

Therefore, running a roscore on a local laptop and accessing it from a container works.

However what doesn’t work, is running a rosmaster in the container and accessing it from the laptop.

When I run roscore on port 80 (in the container) and launch a ros node, XML-RPC server spits an error that the it cannot contact the master.

$ export ROS_MASTER_URI=http://<IP of laptop>:80
$ roslaunch <launchfile>.launch
started roslaunch server http://localhost:37337/

...

auto-starting new master
process[master]: started with pid [878]
ERROR: Unable to start XML-RPC server, port 80 is already in use
Unhandled exception in thread started by <bound method XmlRpcNode.run of <rosgraph.xmlrpc.XmlRpcNode object at 0x7f02189d42d0>>
Traceback (most recent call last):
  File "/opt/ros/kinetic/lib/python2.7/dist-packages/rosgraph/xmlrpc.py", line 215, in run
    self._run()
  File "/opt/ros/kinetic/lib/python2.7/dist-packages/rosgraph/xmlrpc.py", line 284, in _run
    self._run_init()
  File "/opt/ros/kinetic/lib/python2.7/dist-packages/rosgraph/xmlrpc.py", line 234, in _run_init
    self.server = ThreadingXMLRPCServer((bind_address, port), log_requests)
  File "/opt/ros/kinetic/lib/python2.7/dist-packages/rosgraph/xmlrpc.py", line 115, in __init__
    SimpleXMLRPCServer.__init__(self, addr, SilenceableXMLRPCRequestHandler, log_requests)
  File "/usr/lib/python2.7/SimpleXMLRPCServer.py", line 593, in __init__
    SocketServer.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
  File "/usr/lib/python2.7/SocketServer.py", line 417, in __init__
    self.server_bind()
  File "/usr/lib/python2.7/SocketServer.py", line 431, in server_bind
    self.socket.bind(self.server_address)
  File "/usr/lib/python2.7/socket.py", line 228, in meth
    return getattr(self._sock,name)(*args)
socket.error: [Errno 98] Address already in use

I have read in the documentation that for single container applications I do not need to expose ports. Should I try and manually exposing the ports? Does it matter that it I am using it on runtime?

Thank you in advance,

This is getting pretty convoluted, and a bit difficult to untangle without us having that much experience with ROS that you have.

To me the earlier CURL that resulted in a 501 error shows that the endpoint is listening properly - just needs different communication. Otherwise it would be just refusing to connect.

The master that you are trying to connect to is running in a resinOS system, right? Is that okay if we take a look at the system from the inside, so we can be more on the same page? Will send you a private message for the device ID, and can continue the debug on this thread.

Hi, looks like the server is only exposed to localhost, where in the code would one set the server to be on 0.0.0.0 and port 80?

I am running roscore as

roscore -p 80

You cannot set the rosmaster address as 0.0.0.0 as the other ros nodes should be able to connect to the rosmaster

Do you mean that I have to manually expose the port from the dockerfile?

Sorry, can you explain what do you mean by that?

The point being, is that servers run with localhost:port setup are not going to be accessible outside of the machine, while servers run with 0.0.0.0:port should be (as 0.0.0.0 just means that expose the server on all interfaces).
When I check on the host OS:

  • curl -v localhost:80 works as expected, I think, giving a 501 error as the server expects different data, but at least connects
  • curl -v 192.x.x.x:80 which is the local network address on the other hand cannot connect, as expected if the server is just running on localhost.

Thus trying to figure out where’s the discrepancy, if this assessment above is correct

In the meantime we are reading up on ROS concepts, as it’s not totally clear what supposed to be going on, and how the service is being run. Will get back to you with anything that we figure out!