unificontrol icon indicating copy to clipboard operation
unificontrol copied to clipboard

Unable to connect to a UDM Pro with this library

Open richardellwood opened this issue 4 years ago • 82 comments

Attempting to connect to my UDM Pro with the program


from unificontrol import UnifiClient
import ssl

cert = ssl.get_server_certificate(("10.1.0.1", 443))

router = UnifiClient(host = "10.1.0.1", port = "443", username = "admin", password = "[password]", cert = cert)

I get the following error

Traceback (most recent call last):
  File "test.py", line 8, in <module>
    clients = router.list_sites()
  File "/home/sysop/.local/lib/python3.5/site-packages/unificontrol/metaprogram.py", line 123, in wrapper
    return instance(client, *a, **kw)
  File "/home/sysop/.local/lib/python3.5/site-packages/unificontrol/metaprogram.py", line 101, in __call__
    return client._execute(url, self._method, rest_dict, need_login=self._need_login)
  File "/home/sysop/.local/lib/python3.5/site-packages/unificontrol/unifi.py", line 110, in _execute
    response = resp.json()
  File "/home/sysop/.local/lib/python3.5/site-packages/requests/models.py", line 898, in json
    return complexjson.loads(self.text, **kwargs)
  File "/usr/lib/python3.5/json/__init__.py", line 319, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3.5/json/decoder.py", line 339, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python3.5/json/decoder.py", line 357, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 2 column 1 (char 1)

Any advice is appreciated. Richard

richardellwood avatar Nov 09 '20 23:11 richardellwood

Historically by default the Unifi controller is on port 8443, not port 443. If you are running a CloudKey or similar then there is a UI on 443, but it redirects you to port 8443 for the Unifi Network interface and port 7443 for the Unifi Protect interface. Try the using port 8443 and see if that fixes it (or just don't specify the port argument, since it defaults to the right port).

That said, even if that fixes it the error message is unhelpful; I'll put in some more informative error handling when I have some time.

nickovs avatar Nov 09 '20 23:11 nickovs

Thank you, Nickovs. 8443 isn't working for me either. I'm going to try digging into your code and see if I can figure out what's going on. If I find a solution, I'll certainly contribute.

richardellwood avatar Nov 10 '20 18:11 richardellwood

Based on the stack backtrace, it looks like the crash is happening because whatever the server is sending back can not be decoded as a JSON payload even though the HTTP request returned an OK result. This is happening at line 110 in unifi.py. This suggests to me that the endpoint that you are hitting is not the Unifi controller endpoint but something else (hence my suggestion that you check the port).

If you are up for trying to debug this yourself then you probably want to start by printing r.text just before the code tries to extract the reply body as JSON, to see what is actually getting returned.

nickovs avatar Nov 10 '20 19:11 nickovs

I've been running into this issue too, on the latest 1.8.5 firmware.

When I do the print, the output is:

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"><link href="/2.css" rel="stylesheet"></head>
<body>
<div id="root"></div>
<script type="text/javascript" src="/vendor.0306759b.chunk.js"></script><script type="text/javascript" src="/main.d8e6c715.js"></script></body>
</html>

I had to edit metaprogram.py and on L77 change:

return "https://{host}:{port}/api/s/{site}/{endpoint}{path}".format(

to:

return "https://{host}:{port}/proxy/network/api/s/{site}/{endpoint}{path}".format(

avleen avatar Jan 14 '21 15:01 avleen

Likewise, in unifi.py, L142 needs to change to:

"auth/login"

and L161 needs to change to:

"auth/logout"

avleen avatar Jan 14 '21 15:01 avleen

Also in unifi.py, L114, there is on response['data'] object any more. I'm not sure what it used to look like before, but now response is just a hash full of lots of info. I assume that might be what we're looking for? Sorry about the flood of comments, I don't know how you'd want to update the library to support the new firmware with all these changes and still be backwards compatible so I'm dumping this knowledge here :)

avleen avatar Jan 14 '21 15:01 avleen

Ok, hopefully last update, this is a diff of what worked in the end, for both login and various other things. Happy to send a pull request if you want it as is, but it will break people on older firmware for sure: https://github.com/avleen/unificontrol/commit/0c9fcfaa19fe9671e12ac68de054f1e4b47d8e31

avleen avatar Jan 14 '21 16:01 avleen

@avleen Thanks for all of these details. Can you confirm that you are also using a UDM Pro? What version of the server code are you running? I don't currently have access to a UDM Pro but if we can find a way to tell the difference and you are up for some testing then maybe we can build a version of this library that works with the UDM, the CloudKey and the stand-alone Java version.

nickovs avatar Jan 14 '21 16:01 nickovs

Hi Nicko!

I have the UDM (non-pro) device, which has the built-in cloud key. I'm happy to do testing :)

On Thu, Jan 14, 2021 at 4:28 PM Nicko van Someren [email protected] wrote:

@avleen https://github.com/avleen Thanks for all of these details. Can you confirm that you are also using a UDM Pro? What version of the server code are you running? I don't currently have access to a UDM Pro but if we can find a way to tell the difference and you are up for some testing then maybe we can build a version of this library that works with the UDM, the CloudKey and the stand-alone Java version.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/nickovs/unificontrol/issues/12#issuecomment-760307578, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEDXBKQ7ZGEQBDU2GWEO4DSZ4LS7ANCNFSM4TP6Y2HQ .

avleen avatar Jan 15 '21 11:01 avleen

Hello i am running in the same issue . i installed this library with pip install git+git://github.com/avleen/unificontrol.git@0c9fcfaa19fe9671e12ac68de054f1e4b47d8e31 but still getting same error :(

thundergreen avatar Jan 17 '21 22:01 thundergreen

Hello i am running in the same issue . i installed this library with pip install git+git://github.com/avleen/unificontrol.git@0c9fcfaa19fe9671e12ac68de054f1e4b47d8e31 but still getting same error :(

What device and firmware version do you have @thundergreen ?

avleen avatar Jan 17 '21 22:01 avleen

I am running a UDM Pro with firmware 1.8.5 - and willing to test. Though my python skills are very limited. The UDM Pro hosts multiple functions. Apart from the Network Controller there is also Protect, Access and Talk. Which is why the Network API endpoints are at /proxy/network/api/s and the authentication endpoints at /api/auth.

5bomen avatar Jan 18 '21 14:01 5bomen

FYI, I'm also getting the same error with a UDM Base. The Network Controller is 6.0.43, UDM firmware is 1.8.5.

I"m not sure if this helps you detect UDM*, but uname -a (from the UniFi OS shell) gives me: Linux ubnt 4.1.37-v1.8.5.2964-30c04be #1 SMP Tue Dec 29 05:40:16 MST 2020 aarch64 GNU/Linux

Quite a few defaults seem to be different: I believe hostname is always unifi (and default domain is .localdomain), default port is 443, default administrator is root.

The root web application (https://unifi) lets you choose between the Network Controller (URI /network), or managing the UDM itself (/settings).

The latest API, via curl, is here: https://dl.ui.com/unifi/6.0.45-564eef1dda/unifi_sh_api

SinisterStairs avatar Jan 25 '21 21:01 SinisterStairs

It would be helpful if someone (or even several people) with UDMs, CloudKeys and locally installed Java controllers could run:

import requests
print(requests.get("https://<unifi IP address>:8443/status", verify=False).text)
print(requests.get("https://<unifi IP address>:443/status", verify=False).text)

and send me the output.

To save clogging this discussion up, maybe paste the output into a Gist and just post the link here. I'm guessing that this might be enough to let me auto-detect what sort of controller the client is trying to talk to.

nickovs avatar Jan 25 '21 22:01 nickovs

Hey Nicko,

At least on the 1.8.5 firmware on the UDM (non-pro), that URL doesn't work any more:

  1. Port 8443 isn't used any more
  2. /status isn't a valid URL any more, it returns this: https://gist.github.com/avleen/b57408d4da00751baa2b6c6ef1e373f0

On Mon, Jan 25, 2021 at 10:28 PM Nicko van Someren [email protected] wrote:

It would be helpful if someone (or even several people) with UDMs, CloudKeys and locally installed Java controllers could run:

import requestsprint(requests.get("https://<unifi IP address>:8443/status", verify=False).text)print(requests.get("https://<unifi IP address>:443/status", verify=False).text)

and send me the output.

To save clogging this discussion up, maybe paste the output into a Gist https://gist.github.com and just post the link here. I'm guessing that this might be enough to let me auto-detect what sort of controller the client is trying to talk to.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/nickovs/unificontrol/issues/12#issuecomment-767154850, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEDXBPZGZOKTJFDE6YCGKLS3XV7HANCNFSM4TP6Y2HQ .

avleen avatar Jan 25 '21 22:01 avleen

Can you try https://<unifi IP address>:443/proxy/network/status?

nickovs avatar Jan 25 '21 22:01 nickovs

Unauthenticated this doesn't work. Authenticated I get back:


{"meta":{"rc":"ok","uuid":"<uuid_stripped>"},"data":[]}

On Mon, Jan 25, 2021 at 10:50 PM Nicko van Someren [email protected] wrote:

Can you try https://<unifi IP address>:443/proxy/network/status?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/nickovs/unificontrol/issues/12#issuecomment-767163159, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAEDXBJGPVOQSCMU7TW7HYLS3XYUBANCNFSM4TP6Y2HQ .

avleen avatar Jan 25 '21 23:01 avleen

Thanks. When you say "doesn't work", do you get a 401 result? If so, do you get something akin to:

{"meta":{"rc":"error","msg":"api.err.LoginRequired"},"data":[]}

in the body that is returned. Thanks.

nickovs avatar Jan 25 '21 23:01 nickovs

Yes, unauthenticated, the UDM's /proxy/network/status endpoint returns a 401. Response body is simply Unauthorized.

As verification of the response body, the response header contains:

Content-Length: 12
Content-Type:  text/plain; charset=utf-8

...so, not JSON with useful information, just plain text.

SinisterStairs avatar Jan 26 '21 01:01 SinisterStairs

@SinisterStairs Thanks for that, although it's not what I had hoped. What do you get if you try https://<unifi IP address>:443/api/auth/login or https://<unifi IP address>:443/api/auth/login?username=xxxx&password=xxxx ? Do you get a similarly unhelpful response body or do you get JSON with error details?

nickovs avatar Jan 26 '21 03:01 nickovs

Interestingly enough, I typo'd the auth login URL (I used /auth/login instead of /api/auth/login, and got a 200 using basic authentication (response text/html). I get hung up trying to pass in the username/password as query strings to /auth/login.

~~Using /api/auth/login gives me a JSON 401 when using basic authentication, and hangs trying to pass authentication via query strings.~~ EDIT: This was user error, with how I constructed my curl command.

UPDATE: Need sleep, getting sloppy. I lost of track when I used GET or POST, and when I used basic authentication or query strings. By "hung up" I mean no immediate response to curl, and I hit ctrl-c before timing out.

SinisterStairs avatar Jan 26 '21 05:01 SinisterStairs

I have pushed a branch which hopefully will allow support for the Dream Machine as well as the "classic" controller. It's available in the udm-support branch. There is a new server_type argument to the client constructor; if you pass unificontrol.UNIFI_SERVER_UDM then hopefully it should work with the UDM; it defaults to support the classic controller so as not to break existing code.

It would be a big help if some of you could give this a try and tell me if it works. I don't have a UDM so it's basically based on the code @avleen posted. If I manage to lay my hands on a UDM I'll also try to write some reliable auto-detection code but for the moment you'll need to tell it which you're using.

nickovs avatar Jan 31 '21 01:01 nickovs

Thanks so much. I'm getting a unificontrol.exception.UnifiTransportError: 404: Not Found due to invalid credentials.

I tried my UDM's local account, my Unifi Cloud account, and the root SSH password. ~~https://gist.github.com/SinisterStairs/ab2ebbc208b5bcf34f15a3872811467a~~

EDIT: Will play around some more in a bit; just wanted to provide some quick feedback. ~~EDIT2: With curl I get a 405 using /api/auth/login and 200 using /auth/login. Changing unifi.py to use /auth/login didn't make a difference, however. WIth curl, I'm passing in username and password through basic authentication, not as query string parameters.~~ EDIT3: This was errors with my curl command.

SinisterStairs avatar Jan 31 '21 02:01 SinisterStairs

@SinisterStairs Thanks for trying it out. In the mean time I will see if I can find access to a UDM myself.

nickovs avatar Jan 31 '21 14:01 nickovs

I tried to create a read-only user for you on my NC that could call the API w/o editing; but I don't think that's possible. If you're unable to find access to a UDM, maybe we could do a screen share sometime (e.g. Zoom). I'm on US Eastern time.

EDIT: I also wanted to thank you for your persistence, supporting a device you don't even own!

SinisterStairs avatar Jan 31 '21 16:01 SinisterStairs

@SinisterStairs Can you try running the following code before you import unificontrol or attempt to connect to your controller and post what log output you get?

import logging
logging.basicConfig(level=logging.DEBUG)

nickovs avatar Jan 31 '21 20:01 nickovs

I edited both the gist's code and comment with the latest. https://gist.github.com/SinisterStairs/939d6a33521aed0e4e6ab36239e584f7

EDIT: To update my previous comments: Authenticating with /api/auth/login is working via curl; previous errors I reported were due to how I constructed the curl command or invoked the endpoint. My functioning curl command is in the comments within the gist above.

SinisterStairs avatar Feb 01 '21 03:02 SinisterStairs

Thanks for the debug output. Can you try adding a call to client.login() before your call to list_clients()? Since the client uses a pooled HTTP/1.1 connection it's possible that the Unifi proxy somehow marks connection as having failed after the first 401 error thus doesn't accept the login retry.

nickovs avatar Feb 01 '21 13:02 nickovs

I updated the gist with the code including client.login(), and added the output as a new comment. https://gist.github.com/SinisterStairs/939d6a33521aed0e4e6ab36239e584f7

SinisterStairs avatar Feb 01 '21 16:02 SinisterStairs

Thanks. It looks like there are several issues here:

  1. There seems to be some redirection going on (the code tried to request /api/auth/login but the debug message suggests that it got a 200 reply from /auth/login).
  2. It seems that the reply from a successful login on the UDM is in a different format to the reply from all the other API calls
  3. It looks like the proxy might return 404 on all subsequent requests if the first request on an HTTP/1.1 connection returns a 401 error.

The login and logout methods have always been a little anomalous compared to all of the other calls so I think that I will just need to have special case code to deal with these quirks. I will see what I can do.

nickovs avatar Feb 01 '21 17:02 nickovs