Code Monkey home page Code Monkey logo

kibra's Introduction

Kirale Border Router Administration (KiBRA)

This project intends to be a reference implementation of a Thread 1.2 Backbone Border Router for a GNU/Linux Debian host and a KiNOS USB enabled device.

It is written in Python 3, and provides a fast way for third-party developers to test they Thread products´ site and global connectivity, or an starting point for a comercial Border Router implementation.

This project is licensed under the terms of the MIT license.

  • Thread Domains support.

  • Multicast forwarding support.

  • DHCPv6 server (Dibbler) autoconfiguration for DHCPv6-PD or ULA prefixes.

  • NTP client and server, advertised to the Thread network in the DHCP options.

  • Stateful NAT64 (Jool) autoconfguration.

  • DNS64 server (Unbound).

  • mDNS (Avahi) advertisement of MeshCoP Border Agent service in the exterior network, with support for external commissioner.

  • Thread network real-time supervision with Thread Management Framework using a CoAP client (aiocoap).

  • Web based dynamic network visualization.

  • Includes a tool to easily form a big test Thread network with other attached KiNOS devices.

    images/KiBRA-Web.png
  • Port Control Protocol
  • Web based network configuration and commissioning.
  • Multi-Thread interface support.

The KiBRA application requires a Python 3.7 installation and makes use of several PyPI modules, apart from the KiTools module. It has been tested in Debian Buster and Raspbian Buster systems, but it will probably run correctly in many other GNU/Linux distributions.

The required system packages are: avahi-daemon, dibbler-server, iproute2, ip6tables, jool, nmap, ntp, radvd and unbound.

The required Python modules are: aiocoap-kirale, bash, daemonize, kitools and pyroute2.

Install and configure system packages.

apt install avahi-daemon dibbler-server ntp radvd unbound virtualenv -y
echo "" > /etc/dibbler/server.conf

There is no official Debian package for Jool yet, so it needs to be built as explained in the Jool packaging repository and later installed:

apt install jool-dkms_*.deb jool-tools_*.deb -y
apt install /tmp/overlay/kibra/deb/jool-tools_4.0.3-1_armhf.deb

Enable a virtual environment for the Python installation.

apt install virtualenv
python3 -m virtualenv -p /usr/bin/python3 /opt/kirale/pyenv
source /opt/kirale/pyenv/bin/activate

Download and install KiTools, aiocoap and KiBRA. The required Python modules will be auto-installed.

git clone https://github.com/KiraleTech/KiTools.git
cd KiTools
python -m pip install --upgrade .
cd ..
git clone https://github.com/KiraleTech/aiocoap.git
cd aiocoap
git checkout kirale-1.0
python -m pip install --upgrade .
cd ..
git clone https://github.com/KiraleTech/KiBRA.git
cd KiBRA
python -m pip install --upgrade .
cd ..

This will make KiBRA run at startup, as soon as system network is enabled:

cp systemd/kibra.sh /opt/kirale/
cp systemd/kibra.service /etc/systemd/system/
systemctl enable kibra.service

Plug a KTDG102 USB dongle in (not needed if using a KTBRN1) and run the installed script in the virtual environment:

python -m kibra

If everything goes well, the script is going to detect the exterior interface and the connected dongle, and configure the interfaces accordingly. If the dongle USB Ethernet is not enabled, it is enabled by the script. By default, the KiNOS device will perform an energy scan to select a proper IEEE 802.15.4 channel and start a Thread network partition on it as Leader.

Once the interior interface is up, the routing and firewall is configured and the services launched: DHCP, NAT and DNS for the interior interface, and mDNS for the exterior interface. Also the TMF subsystem starts to query the dongle for network information. With this information, the network visualization can be drawn. Open a browser on the exterior interface address to see it. Once more nodes are added to the network, the topology and link qualities will be updated.

To stop the script, just type Ctrl+C and wait until all tasks have been stopped.

The configuration file for the Kirale Border Router is located in /opt/kirale/kibra.cfg and has JSON format. If not provided, it is created automatically at the first start with default values:

{
  "ncp_name": "Test",
  "ncp_commcred": "KIRALE"
}

The user can also force some other configuration options:

{
  "ncp_channel": 20,
  "ncp_commcred": "KIRALE",
  "ncp_name": "MyDongle",
  "ncp_netname": "MyNetwork",
  "ncp_panid": "0xc04b",
  "ncp_role": "leader",
  "ncp_serial": "KTWM102-11+201801+8404D2000000045C"
  "exterior_ifname": "eth0",
  "prefix": "2017:0:0:5::/64"
}

The Kirale Border Router acts as a Border Agent for external commissioners. The Thread Commissioning App can be installed in an Android device and connected to a Wi-Fi access point in the same network as the Border Router.

If KiBRA was started correctly, the Commissioning App should be able to discover the advertised network and ask for the Commissioner Credential in order to access to its management. Once entered (by default: "KIRALE") it should successfully join to the network and allow to scan a QR code.

Tip: Use tcpdump for traffic overview on the interior interface.

Scan the QR code from another KTDG102 USB Dongle enclosure label and it will be added to the Commissioner App entitled joiners list. The only configuration required for the joiner is its desired role, and afterwards it can be booted in the network.

config role med
ifup

The joiner should complete the commissioning with the Commissioning App and appear in the network visualization. To check the correct border Router functioning, enable the debug logs and send a ping request to an Inernet address:

debug module ipv6 icmp
debug level all
ping "kirale.com"

An ICMP echo response should arrive to the joined device.

The KiBRA application can be executed (from another terminal) with the --form option to read the currently running Border Router network credentials and apply them to any plugged-in KTDG102 USB Dongles. Once configured the devices join to the network in out-of-band mode, avoiding the slow commissioning process.

This allows a fast network formation for different testing purposes.

The --clear option can be used to clear the configuration of all attached KTDG102 USB Dongles, and therefore, remove them from the network.

A prebuilt Armbian image for the KTBRN1 is available for download from the Kirale's website.

Once flashed to a Micro SD it is possible to use a serial terminal throught the USB port to access to the system shell. The default credentials are:

User:root
Password:kirale123

You may want to configure keyboard and time zone:

dpkg-reconfigure tzdata
dpkg-reconfigure ntp
dpkg-reconfigure keyboard-configuration
setupcon

The SSH server is enabled by default, and the preset Ethernet IPv4 address is 192.168.75.84.

kibra's People

Contributors

kiraledev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kibra's Issues

Unable to join the KTBRN1 to an existing Thread (1.1) network

Hi,

I'm trying to get the KTBRN1 to join an existing network and become a border router on that network. For this I know the following (not the real values, I know the real ones, I'll use examples here):

  • Channel: 11
  • Master Key: 00112233445566778899aabbccddeeff
  • Network name: OpenThread
  • Extended PAN ID: dead00beef00cafe
  • PAN ID: 0x1234

In theory, the documentation says I can set these as defaults in /opt/kirale/kibra.cfg:

{
  "autostart": "1",
  "bbr_seq": "3",
  "dongle_channel": 11,
  "dongle_commcred": "KIRALE",
  "dongle_name": "Test",
  "dongle_netkey": "0x00112233445566778899aabbccddeeff",
  "dongle_netname": "OpenThread",
  "dongle_panid": "0x1234",
  "dongle_serial": "KTWM102-11+201904+8404D20000000769",
  "dongle_sjitter": "120",
  "dongle_xpanid": "0xdead00beef00cafe",
  "mlr_timeout": "3600",
  "prefix": "fd34:fe56:7891:10::/64",
  "prefix_active": "1",
  "prefix_dhcp": "0",
  "prefix_dua": "0",
  "rereg_delay": "6"
}

After editing kibra.cfg, I reboot… I've also tried service kibra restart, both have the same effect: that effect being that with the exception of netkey, all other parameters are ignored:

stuartl@vk4msl-ws:~$ curl http://10.87.144.140/db/cfg
{
  "action_coapserver": "none",
  "action_dhcp": "none",
  "action_diags": "none",
  "action_dns": "none",
  "action_kibra": "none",
  "action_mdns": "none",
  "action_nat": "none",
  "action_network": "none",
  "action_serial": "none",
  "all_network_bbrs": "ff32:40:fd49:6eed:6c21::3",
  "autostart": "1",
  "bagent_port": "49191",
  "bbr_port": "5683",
  "bbr_seq": "3",
  "bbr_status": "secondary",
  "bridging_mark": "3523217257",
  "bridging_table": "84:04:D2:00:07:69",
  "coap_req": "",
  "discovered": "0",
  "dongle_channel": "24", // *NOT* 11 as requested
  "dongle_commcred": "KIRALE",
  "dongle_heui64": "ff-23-8a-22-6a-0e-7d-7f",
  "dongle_ll": "fe80::604f:4eeb:4ad7:b6f3",
  "dongle_mac": "86:04:d2:00:07:69",
  "dongle_mleid": "fd49:6eed:6c21:0:2b1:f805:5603:5c13",
  "dongle_name": "Test",
  "dongle_netkey": "0x00112233445566778899aabbccddeeff", // This is correct
  "dongle_netname": "kite_cdc9", // *NOT* OpenThread as requested
  "dongle_panid": "0x931e", // *NOT* 0x1234 as requested
  "dongle_prefix": "fd49:6eed:6c21::/64",
  "dongle_rloc": "fd49:6eed:6c21::ff:fe00:5c00",
  "dongle_role": "leader",
  "dongle_secpol": "02a0ff00",
  "dongle_serial": "KTWM102-11+201904+8404D20000000769",
  "dongle_sjitter": "120",
  "dongle_status": "joined",
  "dongle_xpanid": "0x590c555821949d00", // *NOT* 0xdead00beef00cafe as requested
  "dua_next_status": "",
  "exterior_ifname": "eth0",
  "exterior_ifnumber": "3",
  "exterior_ipv4": "10.87.144.140",
  "exterior_ipv6_ll": "fe80::81:deff:fecc:d71",
  "exterior_mac": "02:81:de:cc:0d:71",
  "exterior_port_mc": "49191",
  "interior_ifname": "enx8404d2000769",
  "interior_ifnumber": "5",
  "interior_mac": "84:04:D2:00:07:69",
  "kibra_model": "KTBRN1",
  "kibra_vendor": "Kirale",
  "kibra_version": "KiBRA v1.3.0",
  "mcast_admin_fwd": "1",
  "mcast_out_fwd": "1",
  "mlr_timeout": "3600",
  "prefix": "fd34:fe56:7891:10::/64",
  "prefix_active": "1",
  "prefix_dhcp": "0",
  "prefix_dua": "0",
  "rereg_delay": "6",
  "serial_device": "/dev/ttyACM0",
  "status_coapserver": "running",
  "status_dhcp": "running",
  "status_diags": "running",
  "status_dns": "running",
  "status_kibra": "running",
  "status_mdns": "running",
  "status_nat": "running",
  "status_network": "running",
  "status_serial": "running"
}

Is there some other trick needed to make it use the settings as given?

IP addresses incorrectly prefixed when setting up border router

Hi all,

I'm just trying out the Kirale border router with a KTDG102 dongle… I'm using the virtual machine image which I have running under KVM. At first it refused to see the dongle, but then I tried flashing the dongle firmware (with this image), and things started working, but then the border router would fail setting up the network.

I tweaked the software a little bit to reveal the stack trace and parameters being entered:

root@kiralebr:~/py36env/KiBRA# git diff
diff --git a/kibra/ktask.py b/kibra/ktask.py
index 33a717a..82474ee 100644
--- a/kibra/ktask.py
+++ b/kibra/ktask.py
@@ -93,8 +93,8 @@ class Ktask(Thread):
                         logging.info('Task [%s] has now started.', self.name)
                     except Exception as exc:
                         db.set(self.status_key, status.ERRORED)
-                        logging.error('Task [%s] errored on start: %s',
-                                      self.name, exc)
+                        logging.error('Task [%s] errored on start',
+                                      self.name, exc_info=1)
                     db.set(self.action_key, action.NONE)
                 elif task_action is action.KILL:
                     self.is_alive = False
diff --git a/kibra/network.py b/kibra/network.py
index 75699c1..e0ebe79 100644
--- a/kibra/network.py
+++ b/kibra/network.py
@@ -183,10 +183,10 @@ def _ifup():
     IP.link('set', index=idx, state='up', txqlen=5000)
 
     # Add inside IPv6 addresses
-    logging.info('Configuring interior interface %s with address %s.',
+    logging.info('Configuring interior interface %s with address %r.',
                  db.get('interior_ifname'), db.get('dongle_rloc'))
     IP.addr('add', index=idx, address=db.get('dongle_rloc'), prefixlen=64)
-    logging.info('Configuring interior interface %s with address %s.',
+    logging.info('Configuring interior interface %s with address %r.',
                  db.get('interior_ifname'), db.get('dongle_eid'))
     IP.addr('add', index=idx, address=db.get('dongle_eid'), prefixlen=64)

…which gave me what was going on…

2019-06-28 12:07:35,846 - INFO [network]: Configuring interior interface enx8404d20004a6 with address '[R] fd85:4a3c:b40e::ff:fe00:ac00'.
2019-06-28 12:07:35,850 - ERROR [ktask]: Task [network] errored on start
Traceback (most recent call last):
  File "/root/py36env/KiBRA/kibra/ktask.py", line 91, in run
    self.kstart()
  File "/root/py36env/KiBRA/kibra/network.py", line 313, in kstart
    _ifup()
  File "/root/py36env/KiBRA/kibra/network.py", line 188, in _ifup
    IP.addr('add', index=idx, address=db.get('dongle_rloc'), prefixlen=64)
  File "/root/py36env/lib/python3.6/site-packages/pyroute2/iproute/linux.py", line 1279, in addr
    terminate=lambda x: x['header']['type'] ==
  File "/root/py36env/lib/python3.6/site-packages/pyroute2/netlink/nlsocket.py", line 373, in nlm_request
    return tuple(self._genlm_request(*argv, **kwarg))
  File "/root/py36env/lib/python3.6/site-packages/pyroute2/netlink/nlsocket.py", line 861, in nlm_request
    self.put(msg, msg_type, msg_flags, msg_seq=msg_seq)
  File "/root/py36env/lib/python3.6/site-packages/pyroute2/netlink/nlsocket.py", line 612, in put
    self.sendto_gate(msg, addr)
  File "/root/py36env/lib/python3.6/site-packages/pyroute2/netlink/rtnl/iprsocket.py", line 61, in _gate_linux
    msg.encode()
  File "/root/py36env/lib/python3.6/site-packages/pyroute2/netlink/__init__.py", line 1073, in encode
    offset = self.encode_nlas(offset)
  File "/root/py36env/lib/python3.6/site-packages/pyroute2/netlink/__init__.py", line 1382, in encode_nlas
    nla.encode()
  File "/root/py36env/lib/python3.6/site-packages/pyroute2/netlink/__init__.py", line 1672, in encode
    self['value'] = inet_pton(family, self.value)
OSError: illegal IP address string passed to inet_pton

I don't think that [R] belongs there. If I alter the code a little bit:

diff --git a/kibra/network.py b/kibra/network.py
index 75699c1..997201c 100644
--- a/kibra/network.py
+++ b/kibra/network.py
@@ -183,12 +183,15 @@ def _ifup():
     IP.link('set', index=idx, state='up', txqlen=5000)
 
     # Add inside IPv6 addresses
-    logging.info('Configuring interior interface %s with address %s.',
-                 db.get('interior_ifname'), db.get('dongle_rloc'))
-    IP.addr('add', index=idx, address=db.get('dongle_rloc'), prefixlen=64)
-    logging.info('Configuring interior interface %s with address %s.',
-                 db.get('interior_ifname'), db.get('dongle_eid'))
-    IP.addr('add', index=idx, address=db.get('dongle_eid'), prefixlen=64)
+    addr = db.get('dongle_rloc').split(' ')[-1]
+    logging.info('Configuring interior interface %s with address %r.',
+                 db.get('interior_ifname'), addr)
+    IP.addr('add', index=idx, address=addr, prefixlen=64)
+
+    addr = db.get('dongle_eid').split(' ')[-1]
+    logging.info('Configuring interior interface %s with address %r.',
+                 db.get('interior_ifname'), addr)
+    IP.addr('add', index=idx, address=addr, prefixlen=64)
 
     # Add dongle neighbour
     IP.neigh(

… it gets a little further, but still fails as it then tries to add a multicast address. This is the log I have now:

(py36env) root@kiralebr:~/py36env/KiBRA# python -m kibra
/root/py36env/lib/python3.6/site-packages/pyroute2/__init__.py:6: UserWarning: Module kibra was already imported from None, but /root/py36env/KiBRA is being added to sys.path
  import pkg_resources
2019-06-28 12:22:17,520 - INFO [webserver]: Loading web server...
2019-06-28 12:23:14,592 - INFO [ktask]: Loading task [serial]...
2019-06-28 12:23:14,592 - INFO [ksh]: Trying to find a KiNOS device...
2019-06-28 12:23:14,596 - INFO [ktask]: Loading task [network]...
2019-06-28 12:23:14,597 - INFO [ktask]: Task [network] is waiting for [serial] to start.
2019-06-28 12:23:14,600 - INFO [ktask]: Loading task [dhcp]...
2019-06-28 12:23:14,600 - INFO [ktask]: Task [dhcp] is waiting for [network] to start.
2019-06-28 12:23:14,601 - INFO [ktask]: Loading task [nat]...
2019-06-28 12:23:14,602 - INFO [ktask]: Loading task [dns]...
2019-06-28 12:23:14,603 - INFO [ktask]: Task [dns] is waiting for [network] to start.
2019-06-28 12:23:14,604 - INFO [ktask]: Loading task [mdns]...
2019-06-28 12:23:14,604 - INFO [ktask]: Task [mdns] is waiting for [diags] to start.
2019-06-28 12:23:14,605 - INFO [ktask]: Loading task [diags]...
2019-06-28 12:23:14,605 - INFO [ktask]: Task [diags] is waiting for [serial] to start.
2019-06-28 12:23:14,606 - INFO [ktask]: Task [nat] is waiting for [serial] to start.
2019-06-28 12:23:16,167 - INFO [ksh]: KiNOS device was found on /dev/ttyACM1!
2019-06-28 12:23:16,168 - INFO [ksh]: Serial device is /dev/ttyACM1.
ttyACM1|> show snum
ttyACM1|> KTWM102-11+201802+8404D200000004A6
ttyACM1|> show snum
ttyACM1|> KTWM102-11+201802+8404D200000004A6
ttyACM1|> show hwconfig
ttyACM1|> USB Interface
ttyACM1|>   Serial       : on
ttyACM1|>   DFU          : on
ttyACM1|>   Ethernet     : on
ttyACM1|> UART Interface : off
ttyACM1|> Activity LED   : on
ttyACM1|> Low-Power Mode : off
2019-06-28 12:23:16,272 - INFO [network]: Trying to obtain a prefix via Prefix Delegation...
2019-06-28 12:23:16,272 - INFO [network]: It was not possible to obtain a global prefix.
2019-06-28 12:23:16,272 - INFO [network]: Generated the ULA prefix fdde:5b57:6035::/48.
2019-06-28 12:23:16,275 - INFO [network]: Using 10.87.130.137 as exterior IPv4 address.
2019-06-28 12:23:16,278 - INFO [ksh]: Waiting until dongle is joined...
2019-06-28 12:23:17,286 - INFO [ksh]: Configure dongle comissioner credential KIRALE.
ttyACM1|> config commcred "KIRALE"
2019-06-28 12:23:17,288 - INFO [ksh]: Set dongle as leader.
ttyACM1|> config role leader
ttyACM1|> ifup
2019-06-28 12:23:17,290 - INFO [ksh]: Waiting until dongle is joined...
2019-06-28 12:23:50,328 - INFO [ksh]: Waiting until dongle becomes router...
ttyACM1|> show role
ttyACM1|> leader
2019-06-28 12:23:50,345 - INFO [ksh]: Waiting until dongle becomes router...
ttyACM1|> show role
ttyACM1|> leader
ttyACM1|> show role
ttyACM1|> leader
ttyACM1|> show status
ttyACM1|> joined
ttyACM1|> show ipaddr
ttyACM1|> [R] fe80::d413:246f:a08f:f07f
ttyACM1|> [R] fd08:1f7f:e7a3:0:f8bf:a281:9c6d:4033
ttyACM1|> [R] fd08:1f7f:e7a3::ff:fe00:9400
ttyACM1|> [R] ff02::1
ttyACM1|> [R] ff03::1
ttyACM1|> [R] ff33:40:fd08:1f7f:e7a3::1
ttyACM1|> [R] ff32:40:fd08:1f7f:e7a3::1
ttyACM1|> [R] ff02::2
ttyACM1|> [R] ff03::2
ttyACM1|> [R] ff03::fc
2019-06-28 12:23:50,456 - INFO [ksh]: EID address is [R] fe80::d413:246f:a08f:f07f.
2019-06-28 12:23:50,456 - INFO [ksh]: EID address is [R] fd08:1f7f:e7a3:0:f8bf:a281:9c6d:4033.
2019-06-28 12:23:50,456 - INFO [ksh]: RLOC address is [R] fd08:1f7f:e7a3::ff:fe00:9400.
2019-06-28 12:23:50,456 - INFO [ksh]: EID address is [R] ff02::1.
2019-06-28 12:23:50,457 - INFO [ksh]: EID address is [R] ff03::1.
2019-06-28 12:23:50,457 - INFO [ksh]: EID address is [R] ff33:40:fd08:1f7f:e7a3::1.
2019-06-28 12:23:50,457 - INFO [ksh]: EID address is [R] ff32:40:fd08:1f7f:e7a3::1.
2019-06-28 12:23:50,457 - INFO [ksh]: EID address is [R] ff02::2.
2019-06-28 12:23:50,457 - INFO [ksh]: EID address is [R] ff03::2.
2019-06-28 12:23:50,457 - INFO [ksh]: EID address is [R] ff03::fc.
ttyACM1|> config brouter on
ttyACM1|> Command not allowed
2019-06-28 12:23:50,465 - INFO [ksh]: Border router has been enabled.
ttyACM1|> config bagent on
2019-06-28 12:23:50,465 - INFO [ksh]: Border router has been enabled.
ttyACM1|> config bagent on
2019-06-28 12:23:50,467 - INFO [ksh]: Border agent has been enabled.
2019-06-28 12:23:50,467 - INFO [ktask]: Task [serial] has now started.
2019-06-28 12:23:50,931 - INFO [ktask]: Task [diags] is waiting for [network] to start.
2019-06-28 12:23:50,999 - INFO [network]: Forwarding has been enabled.
2019-06-28 12:23:51,003 - INFO [network]: DAD has been disabled for enx8404d20004a6.
2019-06-28 12:23:51,007 - INFO [network]: Configuring interior interface enx8404d20004a6 with address 'fd08:1f7f:e7a3::ff:fe00:9400'.
2019-06-28 12:23:51,007 - INFO [network]: Configuring interior interface enx8404d20004a6 with address 'ff03::fc'.
2019-06-28 12:23:51,008 - ERROR [ktask]: Task [network] errored on start
Traceback (most recent call last):
  File "/root/py36env/KiBRA/kibra/ktask.py", line 91, in run
    self.kstart()
  File "/root/py36env/KiBRA/kibra/network.py", line 316, in kstart
    _ifup()
  File "/root/py36env/KiBRA/kibra/network.py", line 194, in _ifup
    IP.addr('add', index=idx, address=addr, prefixlen=64)
  File "/root/py36env/lib/python3.6/site-packages/pyroute2/iproute/linux.py", line 1279, in addr
    terminate=lambda x: x['header']['type'] ==
  File "/root/py36env/lib/python3.6/site-packages/pyroute2/netlink/nlsocket.py", line 373, in nlm_request
    return tuple(self._genlm_request(*argv, **kwarg))
  File "/root/py36env/lib/python3.6/site-packages/pyroute2/netlink/nlsocket.py", line 864, in nlm_request
    callback=callback):
  File "/root/py36env/lib/python3.6/site-packages/pyroute2/netlink/nlsocket.py", line 376, in get
    return tuple(self._genlm_get(*argv, **kwarg))
  File "/root/py36env/lib/python3.6/site-packages/pyroute2/netlink/nlsocket.py", line 701, in get
    raise msg['header']['error']
pyroute2.netlink.exceptions.NetlinkError: (99, 'Cannot assign requested address')

Kirale DFU Version does not match Downloads

Current version in kibra/init.py doesn't match the DFU that is publicly available on the the kirale.com/support#downloads site.

Suggested fix.

Match the version from 1.3.... to current version 1.2.7274.47206

db/leases not found

When accessing the webserver from the browser. Browser reports back that db/leases is not found. It is not loaded in sources either. What is required to make it acessible ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.