Giter Site home page Giter Site logo

pystun's Introduction

image

image

PyStun

A Python STUN client for getting NAT type and external IP

This is a fork of pystun originally created by gaohawk (http://code.google.com/p/pystun/)

PyStun follows RFC 3489: http://www.ietf.org/rfc/rfc3489.txt

A server following STUN-bis hasn't been found on internet so RFC3489 is the only implementation.

Installation

To install the latest version:

$ pip install pystun

or download/clone the source and install manually using:

$ cd /path/to/pystun/src
$ python setup.py install

If you're hacking on pystun you should use the 'develop' command instead:

$ python setup.py develop

This will make a link to the sources inside your site-packages directory so that any changes are immediately available for testing.

Usage

From command line:

$ pystun
NAT Type: Full Cone
External IP: <your-ip-here>
External Port: 54320

Pass --help for more options:

% pystun --help
usage: pystun [-h] [-d] [-H STUN_HOST] [-P STUN_PORT] [-i SOURCE_IP]
              [-p SOURCE_PORT] [--version]

optional arguments:
  -h, --help            show this help message and exit
  -d, --debug           Enable debug logging (default: False)
  -H STUN_HOST, --host STUN_HOST
                        STUN host to use (default: None)
  -P STUN_PORT, --host-port STUN_PORT
                        STUN host port to use (default: 3478)
  -i SOURCE_IP, --interface SOURCE_IP
                        network interface for client (default: 0.0.0.0)
  -p SOURCE_PORT, --port SOURCE_PORT
                        port to listen on for client (default: 54320)
  --version             show program's version number and exit

From Python:

import stun
nat_type, external_ip, external_port = stun.get_ip_info()

This will rotate through an internal list of STUN servers until a response is found. If no response is found you will get "Blocked" as the nat_type and None for external_ip and external_port.

If you prefer to use a specific STUN server:

nat_type, external_ip, external_port = stun.get_ip_info(stun_host='stun.ekiga.net')

If you prefer to use a specific STUN server port:

nat_type, external_ip, external_port = stun.get_ip_info(stun_port=3478)

You may also specify the client interface and port that is used although this is not needed:

sip = "0.0.0.0" # interface to listen on (all)
port = 54320 # port to listen on
nat_type, external_ip, external_port = stun.get_ip_info(sip, port)

Read the code for more details...

LICENSE

MIT

pystun's People

Contributors

b1naryth1ef avatar jtriley avatar keyneom avatar renelvon avatar

Stargazers

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

Watchers

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

pystun's Issues

Is pystun still being maintained?

It seems like there isn't much activity here and we at the OpenBazaar project would like to enhance or improve some of the code here. We would much rather push these upstream if the project is still alive, but if not please let us know so we can seek out a different path.

Thanks!

Logic error in handling error response

in the logic of stun_test, there's code as below around line

    # around line 113:
    recvCorr = False
    while not recvCorr:
        # around line 136,
        bind_resp_msg = dictValToMsgType[msgtype] == "BindResponseMsg"
        tranid_match = tranid.upper() == binascii.b2a_hex(buf[4:20]).upper()
        if bind_resp_msg and tranid_match:

It does not handle the logic which the stun server return BindErrorResponseMsg, which lead to infinite loop.

This can be reproduce with a self setup stun server, and run the function with source ip = '0.0.0.0'.

But it's not a very big problem, I suggest to throw an error in this situation.
Because the server did response, but just it's an error response.

Thanks.

Question about STUN server

I'm an undergraduate student working on NAT traversal problem by making use of STUN and TURN. Thanks a lot for your implementation of this STUN client.
What I want to know is that is there any implementations of STUN server in Python? Or any other implementations that work well with your version of STUN client?
PS: I found some samples written in Node.js (from github) and C/C++ (from other references).

Installation on IronPython error

how fix it?
output:

Installing 'pystun'
Unhandled exception:
Traceback (most recent call last):
File "C:\Program Files (x86)\IronPython 2.7\Lib\runpy.py", line 182, in run_module
File "C:\Program Files (x86)\IronPython 2.7\Lib\runpy.py", line 111, in get_module_details
File "C:\Program Files (x86)\IronPython 2.7\Lib\site-packages\pip_init
.py", line 13, in
File "C:\Program Files (x86)\IronPython 2.7\Lib\site-packages\pip\exceptions.py", line 6, in
File "C:\Program Files (x86)\IronPython 2.7\Lib\site-packages\pip_vendor\six.py", line 701, in
File "C:\Program Files (x86)\IronPython 2.7\Lib\site-packages\pip_vendor\six.py", line 692, in exec_
AttributeError: 'module' object has no attribute '_getframe'
'pystun' failed to install. Exit code: 1

UnboundLocalError

pystun from pip

trying dead stun server i have

nat_type, external_ip, external_port = stun.get_ip_info(stun_host='stun.sipgate.net')
Traceback (most recent call last):
File "", line 1, in
File "/usr/lib/python2.7/site-packages/stun/init.py", line 237, in get_ip_info
stun_host=stun_host)
File "/usr/lib/python2.7/site-packages/stun/init.py", line 206, in get_nat_type
ret = stun_test(s, host, port, source_ip, source_port, changeRequest)
UnboundLocalError: local variable 'host' referenced before assignment

with alive server it is ok

always getting 54320 as external port

is that the expected behavior?

I see that get_ip_info defines 54320 as the default external port, but if I remember correctly, whenever I invoked get_ip_info (about a month ago) I'd get the port the STUN server would see me through, or am I hallucinating?

dict_item indexing issue in Python 3.5

Hi

I am encountering below mentioned exception only while running Pystun on Python 3.5. The issue can be resolved if you cast the output of line 91,95 in init.py as a list. Please let me know once the fix is part of the main release stream.

nat_type, external_ip, external_port = stun.get_ip_info(stun_host=(stun_details[0]), stun_port=int(stun_details[1]))
File "C:\Users\User\AppData\Local\Programs\Python\Python35\lib\site-packages\stun_init_.py", line 253, in get_ip_info
stun_host=stun_host, stun_port=stun_port)
File "C:\Users\User\AppData\Local\Programs\Python\Python35\lib\site-packages\stun_init_.py", line 186, in get_nat_type
initialize()
File "C:\Users\User\AppData\Local\Programs\Python\Python35\lib\site-packages\stun_init
.py", line 93, in _initialize
dictValToAttr.update({items[i][1]: items[i][0]})
TypeError: 'dict_items' object does not support indexing

[20170407 16:03:15.022] WARNING:Could not find pyasn1 and pyasn1_modules. SSL certificate COULD NOT BE VERIFIED.
stun_host=stun_host, stun_port=stun_port)
File "C:\Users\User\AppData\Local\Programs\Python\Python35\lib\site-packages\stun_init_.py", line 186, in get_nat_type
initialize()
File "C:\Users\User\AppData\Local\Programs\Python\Python35\lib\site-packages\stun_init
.py", line 96, in _initialize
dictValToMsgType.update({items[i][1]: items[i][0]})
TypeError: 'dict_items' object does not support indexing

how set passwd and username

how use:
{'urls':'turn:turn.bistri.com:80?transport=udp','credential':'homeo','username':'homeo'},
{'urls':'turn:webrtcweb.com:4455','credential':'muazkh','username':'muazkh'},
{'urls':'stun:webrtcweb.com:7788?transport=udp','credential':'muazkh','username':'muazkh'},
{'urls':'turn:webrtcweb.com:7788','credential':'muazkh','username':'muazkh'},

{'urls':'turn:turn.anyfirewall.com:443?transport=tcp','credential':'webrtc','username':'webrtc'},

KeyError: b'0101' issue in Python 3.5

Hi

I am encountering below mentioned error after applying the fix for Issue #21.

nat_type, external_ip, external_port = stun.get_ip_info(stun_host=(stun_details[0]), stun_port=int(stun_details[1]))
File "C:\Users\User\AppData\Local\Programs\Python\Python35\lib\site-packages\stun_init_.py", line 253, in get_ip_info
stun_host=stun_host, stun_port=stun_port)
File "C:\Users\User\AppData\Local\Programs\Python\Python35\lib\site-packages\stun_init_.py", line 191, in get_nat_type
ret = stun_test(s, stun_host, port, source_ip, source_port)
File "C:\Users\User\AppData\Local\Programs\Python\Python35\lib\site-packages\stun_init_.py", line 136, in stun_test
bind_resp_msg = dictValToMsgType[msgtype] == "BindResponseMsg"
KeyError: b'0101'

[Bug] Never returns OpenInternet if explicit bind address not specified

Because it compares 0.0.0.0 with some meaningful IP returned by the stun server.

root@audrius:# pystun
NAT Type: Full Cone
External IP: 178.62.69.XXX
External Port: 54320
root@audrius:
# pystun -i 178.62.69.XXX
NAT Type: Open Internet
External IP: 178.62.69.XXX
External Port: 54320

KeyError: '0100'

Sometimes calling get_nat_type() fails with KeyError: '0100'.

Truncated traceback:

  File "/Users/ulo/Envs/test/lib/python2.7/site-packages/stun/__init__.py", line 219, in get_nat_type
    changeRequest)
  File "/Users/ulo/Envs/test/lib/python2.7/site-packages/stun/__init__.py", line 136, in stun_test
    bind_resp_msg = dictValToMsgType[msgtype] == "BindResponseMsg"
KeyError: '0100'

According to https://tools.ietf.org/html/rfc5389#appendix-A and https://tools.ietf.org/html/rfc5389#section-18.1 0100 seems to be a success message for reserved method id 0x000 (which admittedly doesn't make much sense).

I'm not sure what the correct behaviour would be here, however a KeyError seems to be the wrong one ;)

Broken on Python 3.11

Does not work with python 3.11. Fails with traceback:

Traceback (most recent call last):
  File "/home/camargo/venv-pystun/bin/pystun", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/home/camargo/venv-pystun/lib/python3.11/site-packages/stun/cli.py", line 51, in main
    nat_type, external_ip, external_port = stun.get_ip_info(
                                           ^^^^^^^^^^^^^^^^^
  File "/home/camargo/venv-pystun/lib/python3.11/site-packages/stun/__init__.py", line 252, in get_ip_info
    nat_type, nat = get_nat_type(s, source_ip, source_port,
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/camargo/venv-pystun/lib/python3.11/site-packages/stun/__init__.py", line 186, in get_nat_type
    _initialize()
  File "/home/camargo/venv-pystun/lib/python3.11/site-packages/stun/__init__.py", line 93, in _initialize
    dictValToAttr.update({items[i][1]: items[i][0]})
                          ~~~~~^^^
TypeError: 'dict_items' object is not subscriptable

NAT type detect run "Do Test3" had a BUG

hi, i am using pystun to detect NAT type.When i switch NAT type to Restric cone NAT using iptables commands.it will get NAT types is Port Restric Cone NAT.and i use STUN server website :stun.pjsip.org.
But,when i modify get_nat_type function in 257 lines :

log.debug("Do Test3")
ret = stun_test(s, changedIP, port, source_ip, source_port,
                        changePortRequest)

modified:

log.debug("Do Test3")
ret = stun_test(s, stun_host, port, source_ip, source_port,
                        changePortRequest)

it can detect to Restric Cone NAT.i think iptables will accept data when the ip and port had linked ,also it has not overtime.
more information:
iptables config:

[docker@host TUTK]$ sudo iptables-save
# Generated by iptables-save v1.4.21 on Mon Feb  5 17:23:56 2018
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A PREROUTING -i eth0 -j DNAT --to-destination 172.17.0.5
-A POSTROUTING -o eth0 -j SNAT --to-source 66.112.215.237
COMMIT
# Completed on Mon Feb  5 17:23:56 2018
# Generated by iptables-save v1.4.21 on Mon Feb  5 17:23:56 2018
*filter
:INPUT ACCEPT [10:768]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [6:704]
-A FORWARD -i eth0 -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -s 139.162.62.29/32 -j ACCEPT
-A FORWARD -d 172.17.0.5/32 -m state --state NEW -j DROP
COMMIT
# Completed on Mon Feb  5 17:23:56 2018
[root@6fee1bb9f32e pystun]# pystun -d -H  139.162.62.29 -i 172.17.0.5  -p 12342
DEBUG:pystun:Do Test1
DEBUG:pystun:sendto: ('139.162.62.29', 3478)
DEBUG:pystun:recvfrom: ('139.162.62.29', 3478)
DEBUG:pystun:Result: {'ExternalIP': '66.112.215.237', 'Resp': True, 'ExternalPort': 12342, 'ChangedPort': 3478, 'SourcePort': 3479, 'SourceIP': '139.162.62.29', 'ChangedIP': '45.118.135.233'}
DEBUG:pystun:Do Test2
DEBUG:pystun:sendto: ('139.162.62.29', 3478)
DEBUG:pystun:sendto: ('139.162.62.29', 3478)
DEBUG:pystun:sendto: ('139.162.62.29', 3478)
DEBUG:pystun:sendto: ('139.162.62.29', 3478)
DEBUG:pystun:Result: {'ExternalIP': None, 'Resp': False, 'ExternalPort': None, 'ChangedPort': None, 'SourcePort': None, 'SourceIP': None, 'ChangedIP': None}
DEBUG:pystun:Do Test1
DEBUG:pystun:sendto: ('45.118.135.233', 3478)
DEBUG:pystun:recvfrom: ('45.118.135.233', 3478)
DEBUG:pystun:Result: {'ExternalIP': '66.112.215.237', 'Resp': True, 'ExternalPort': 12342, 'ChangedPort': 3479, 'SourcePort': 3478, 'SourceIP': '45.118.135.233', 'ChangedIP': '139.162.62.29'}
DEBUG:pystun:Do Test3
DEBUG:pystun:sendto: ('139.162.62.29', 3478)
DEBUG:pystun:sendto: ('139.162.62.29', 3478)
DEBUG:pystun:sendto: ('139.162.62.29', 3478)
DEBUG:pystun:sendto: ('139.162.62.29', 3478)
DEBUG:pystun:Result: {'ExternalIP': None, 'Resp': False, 'ExternalPort': None, 'ChangedPort': None, 'SourcePort': None, 'SourceIP': None, 'ChangedIP': None}
NAT Type: Restric Port NAT
External IP: None
External Port: None

modified:

[root@6fee1bb9f32e pystun]# pystun -d -H  139.162.62.29 -i 172.17.0.5  -p 12342
DEBUG:pystun:Do Test1
DEBUG:pystun:sendto: ('139.162.62.29', 3478)
DEBUG:pystun:recvfrom: ('139.162.62.29', 3478)
DEBUG:pystun:Result: {'ExternalIP': '66.112.215.237', 'Resp': True, 'ExternalPort': 12342, 'ChangedPort': 3478, 'SourcePort': 3479, 'SourceIP': '139.162.62.29', 'ChangedIP': '45.118.135.233'}
DEBUG:pystun:Do Test2
DEBUG:pystun:sendto: ('139.162.62.29', 3478)
DEBUG:pystun:sendto: ('139.162.62.29', 3478)
DEBUG:pystun:sendto: ('139.162.62.29', 3478)
DEBUG:pystun:sendto: ('139.162.62.29', 3478)
DEBUG:pystun:Result: {'ExternalIP': None, 'Resp': False, 'ExternalPort': None, 'ChangedPort': None, 'SourcePort': None, 'SourceIP': None, 'ChangedIP': None}
DEBUG:pystun:Do Test1
DEBUG:pystun:sendto: ('45.118.135.233', 3478)
DEBUG:pystun:recvfrom: ('45.118.135.233', 3478)
DEBUG:pystun:Result: {'ExternalIP': '66.112.215.237', 'Resp': True, 'ExternalPort': 12342, 'ChangedPort': 3479, 'SourcePort': 3478, 'SourceIP': '45.118.135.233', 'ChangedIP': '139.162.62.29'}
DEBUG:pystun:Do Test3
DEBUG:pystun:sendto: ('139.162.62.29', 3478)
DEBUG:pystun:recvfrom: ('139.162.62.29', 3479)
DEBUG:pystun:Result: {'ExternalIP': '66.112.215.237', 'Resp': True, 'ExternalPort': 12342, 'ChangedPort': 3478, 'SourcePort': 3478, 'SourceIP': '139.162.62.29', 'ChangedIP': '45.118.135.233'}
NAT Type: Restric NAT
External IP: 66.112.215.237
External Port: 12342

[Bug] The wrong way to determine the port restricted NAT or restricted NAT

When we do Test3 to determine the client is behind a restricted or port restricted NAT, this line:

ret = stun_test(s, changedIP, port, source_ip, source_port, changePortRequest)

will request stun server sends a response from addr(changedIP:changedPort). As we have just send a request:

ret = stun_test(s, changedIP, changedPort, source_ip, source_port)

both RestricNAT and RestricPortNAT will receive the response. So we alway get typ = RestricNAT even if behind a port restricted NAT.

why not test mapping rules?

According to the definition of rfc3489, "A cone NAT is one where all requests from the same internal IP address and port are mapped to the same external IP address and port."
But pystun seems to only send one external IP, and then use this external IP Return, and then use another external IP to send to pystun. If pystun receives it, it will be judged as full cone NAT.
I am wondering why not try to use pystun to send to the second external IP to test the mapping rules? Isn't this the basic premise of the Cone NAT?

KeyError: u'0800'

From time to time I get the following exception:

Exception in thread StunReq:
Traceback (most recent call last):
File "/usr/lib/python2.7/threading.py", line 801, in __bootstrap_inner
self.run()
File "/usr/lib/python2.7/threading.py", line 754, in run
self.__target(*self.__args, **self._kwargs)
File "stuntask.py", line 24, in run
send_stun_request()
File "stuntask.py", line 94, in send_stun_request
nat_type, ext_ip, ext_port = stun.get_ip_info(addr, 4789)
File "/home/oferg/mycode/pystun3-1.0.0/stun/init.py", line 275, in get_ip_info
stun_host=stun_host, stun_port=stun_port)
File "/home/oferg/mycode/pystun3-1.0.0/stun/init.py", line 215, in get_nat_type
ret = stun_test(s, stun_host
, port, source_ip, source_port)
File "/home/oferg/mycode/pystun3-1.0.0/stun/init.py", line 150, in stun_test
bind_resp_msg = dictValToMsgType[msgtype] == "BindResponseMsg"
KeyError: u'0800'

What can be the cause of this? I can try and catch the exception, but I have no idea what to do with it :-)

Need to add that I have changed the list of STUN server to a more current one:
STUN_SERVERS = (
'stun.l.google.com:19302',
'stun1.l.google.com:19302',
'stun2.l.google.com:19302',
'stun3.l.google.com:19302',
'stun4.l.google.com:19302',
'stun.ekiga.net'
)

Also add handling of STUN server addresses which has port in it, as in the above list (simple split(':'), nothing fancy).

Thanks!

TypeError: 'dict_items' object is not subscriptable

$ pip3 install pystun
Collecting pystun
Downloading pystun-0.1.0.tar.gz (6.3 kB)
Preparing metadata (setup.py) ... done
Building wheels for collected packages: pystun
Building wheel for pystun (setup.py) ... done
Created wheel for pystun: filename=pystun-0.1.0-py3-none-any.whl size=6481 sha256=49ffd31fb302af0bea1b41fbe0658dca4d143c1b044da2998a74f0b4139e3ba9
Stored in directory: Library/Caches/pip/wheels/6c/5c/b6/c718b0ecd36283aabd9ef70c1c5183ed4536f03c8565bd5e0a
Successfully built pystun
Installing collected packages: pystun

$ pystun

Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.9/bin/pystun", line 8, in <module>
    sys.exit(main())
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/stun/cli.py", line 51, in main
    nat_type, external_ip, external_port = stun.get_ip_info(
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/stun/__init__.py", line 252, in get_ip_info
    nat_type, nat = get_nat_type(s, source_ip, source_port,
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/stun/__init__.py", line 186, in get_nat_type
    _initialize()
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/stun/__init__.py", line 93, in _initialize
    dictValToAttr.update({items[i][1]: items[i][0]})
TypeError: 'dict_items' object is not subscriptable
51pwn@51pwns-MacBook-Pro mywebapp $ pystun
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.9/bin/pystun", line 8, in <module>
    sys.exit(main())
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/stun/cli.py", line 51, in main
    nat_type, external_ip, external_port = stun.get_ip_info(
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/stun/__init__.py", line 252, in get_ip_info
    nat_type, nat = get_nat_type(s, source_ip, source_port,
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/stun/__init__.py", line 186, in get_nat_type
    _initialize()
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/stun/__init__.py", line 93, in _initialize
    dictValToAttr.update({items[i][1]: items[i][0]})
TypeError: 'dict_items' object is not subscriptable

Fixed multiple bugs with pystun and added async support

Hello everyone,

Over the past year I've been working on a new p2p networking library in Python. The software makes heavy use of STUN and uses this library as the basis for NAT enumeration, address lookups, and mapping queries. The software is great. But presently there are a few bugs in the NAT test logic and validation code. The socket code is also 'blocking' so if it's used in a Python program it will prevent anything else from running.

I've fixed these issues and added support for a large number of fallback STUN servers.
My code fully supports IPv6 and TCP, too.

Here's a usage example:

Start the REPR with await support
python3 -m asyncio

from p2pd import *

netifaces = await init_p2pd()

# Load default interface.
i = await Interface() 

# Do STUN calls.
s = STUNClient(interface=i)
wan_ip = await s.get_wan_ip()
mapping = await s.get_mapping(TCP, do_close=1)
nat_info = await s.get_nat_info()

If you print nat_info you'll notice it also includes information on the NATs port allocation behaviour and any observed patterns. The other functions work similarly to pystun. You can also call await i.load_nat() to have the interface load it's associated NAT information.

More information on async network programming can be found at p2pd.net.

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.