VirtualSquare wiki
This is the markdown source for the web site: http://wiki.virtualsquare.org/
It is based on Docsify
License: GNU General Public License v2.0
VirtualSquare wiki
This is the markdown source for the web site: http://wiki.virtualsquare.org/
It is based on Docsify
When compiling the latest master on macOS High Sierra, clang points out a couple buffer overflows in your code:
tcp_subr.c:341:4: warning: '__builtin___strncpy_chk' will always overflow destination buffer [-Wbuiltin-memcpy-chk-size]
strncpy(addr.sun_path,path,UNIX_PATH_MAX);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/secure/_string.h:124:3: note: expanded from macro 'strncpy'
__builtin___strncpy_chk (dest, __VA_ARGS__, __darwin_obsz (dest))
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Here, it looks like you're copying UNIX_PATH_MAX
bytes to the sun_path
field of a struct sockaddr_un
, and in tcp2unix.h you've defined UNIX_PATH_MAX
as 108, which as far as I can tell is correct for Linux systems, but based on cursory research, it seems that different operating systems use different sizes for this field, down to as small as 92 bytes. On my macOS system, the sun_path
field's length is 104 bytes.
vder_packet.c:207:5: warning: '__builtin___memcpy_chk' will always overflow destination buffer [-Wbuiltin-memcpy-chk-size]
memcpy(foot, footprint(packet), sizeof(struct iphdr) + 8);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/secure/_string.h:62:3: note: expanded from macro 'memcpy'
__builtin___memcpy_chk (dest, __VA_ARGS__, __darwin_obsz0 (dest))
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In this case, I think the problem is that you're allocating the variable foot
based on the size of a pointer rather than the size of the struct that the pointer points to.
Building the latest vde-2 from master in parallel (i.e. make -j8
) still fails as reported on SourceForge back in 2012:
$ make -j8
Making all in include
/Applications/Xcode.app/Contents/Developer/usr/bin/make all-am
make[2]: Nothing to be done for `all-am'.
Making all in src
Making all in common
CC cmdparse.lo
CC canonicalize.lo
CC poll.lo
CCLD libvdecommon.la
Making all in lib
Making all in .
make[3]: *** No rule to make target `../../src/lib/libvdemgmt.la', needed by `libvdesnmp.la'. Stop.
make[3]: *** Waiting for unfinished jobs....
CC libvdemgmt.lo
CC libvdesnmp.lo
make[2]: *** [all-recursive] Error 1
make[1]: *** [all-recursive] Error 1
make: *** [all-recursive] Error 1
Compiling the latest master fails with this error:
vde_cryptcab_client.c:136:14: error: use of undeclared identifier 'PATH_MAX'
char source[PATH_MAX], dest[PATH_MAX];
^
vde_cryptcab_client.c:136:30: error: use of undeclared identifier 'PATH_MAX'
char source[PATH_MAX], dest[PATH_MAX];
^
PATH_MAX
is defined in (one of the system headers included by) <limits.h>, which has not been included.
Looks like you've fixed this type of problem before in 41e4208.
Adding #include <limits.h>
does fix the problem, though I don't know in which file or on which line you would like to add it.
hi,
I used slirpvde
to implement very simply a DHCP server on my VDE switch : given the abandonment of slirpvde
, is there an alternative as simple ?
regards, lacsaP.
Starting with commit 888a88a (Jan 15, 2014), vde_switch
can no longer start up when stdin is closed:
$ ./vde_switch </dev/null
./vde_switch: EOF on stdin, cleaning up and exiting
This is a blocker for running vde_switch
via macOS launchd.
(launchd doesn't support vde_switch --daemon
)
cc @jandubois
Hi,
Since Python 2 has been sunset as of Jan 1, 2020 (https://www.python.org/doc/sunset-python-2/), I was wondering if there were plans to update the bindings to support Python 3.
Thanks.
I couldn't find any info how to use vde_router and what can be done with the associated conf file ?
Can the router have multiple WAN ports. Can you assign/spoof mac address for each WAN port? What type of cables can you put in WAN port ?
And generally how to use it.
When compiling the latest master, clang pointed out that control may reach the end of a non-void function:
cryptcab.c:104:1: warning: control may reach end of non-void function [-Wreturn-type]
}
^
Hello,
In vde_switch, VLAN tagged packets are dropped by default, i.e. when the port VLAN is set to 0.
From what I see in the code, packets are only accepted if their VLAN is enabled for the port. But it seems not possible for a port to be in several VLANs at the same time (i.e. act as a trunk). See portsetvlan().
To me, the default behavior for a switch should be to ignore VLANs in packets. Am I wrong?
To reproduce the issue:
# start vde switch
vde_switch
# plug 2 tap interfaces
sudo vde_plug2tap tap0 &
sudo vde_plug2tap tap1 &
# configure tap0
sudo ip link set tap0 up
sudo ip addr add 192.168.50.1/24 dev tap0
# configure tap1 in another netns, so we can ping
sudo ip netns add temp
sudo ip link set tap1 netns temp
sudo ip netns exec temp ip link set tap1 up
sudo ip netns exec temp ip addr add 192.168.50.2/24 dev tap1
# in a dedicated terminal, dump traffic on tap1
sudo ip netns exec temp tcpdump -i tap1 -n -N -l -e
# check ping
ping -c 1 192.168.50.2
# using scapy, forge an ethernet packet, it passes the switch
p = Ether()/IP(dst='192.168.50.2')/UDP()
sendp(p, iface='tap0')
# a VLAN packet is dropped
p = Ether()/Dot1Q(vlan=1)/IP(dst='192.168.50.2')/UDP()
sendp(p, iface='tap0')
# remove temporary netns when test is done
sudo ip netns delete temp
Here is a draft patch, please let me know if I should submit a PR:
From 416538eb4fff1f7c685e8c9303abfafcdfbbeba0 Mon Sep 17 00:00:00 2001
From: Olivier Matz <[email protected]>
Date: Fri, 17 Nov 2023 12:07:55 +0100
Subject: [PATCH] vde_switch/port: ignore VLAN by default
Currently, VLAN tagged packets are dropped by default, i.e. when the
port VLAN is set to 0.
Packets are only accepted if their VLAN is enabled for the port. But it
is not possible for a port to be in several VLANs at the same
time (i.e. act as a trunk). See portsetvlan() to be convinced.
When port VLAN is set to 0, do not read the VLAN header, always accept
the packet as-is (as if it is not tagged). In other words, these ports
will act as trunks that are active for all VLANs.
Signed-off-by: Olivier Matz <[email protected]>
---
src/vde_switch/port.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/vde_switch/port.c b/src/vde_switch/port.c
index 016fbf1..a5c3449 100644
--- a/src/vde_switch/port.c
+++ b/src/vde_switch/port.c
@@ -608,7 +608,10 @@ void handle_in_packet(struct endpoint *ep, struct packet *packet, int len)
SEND_PACKET_PORT(portv[i],i,packet,len,&tmpbuf);
#endif
} else { /* This is a switch, not a HUB! */
- if (packet->header.proto[0] == 0x81 && packet->header.proto[1] == 0x00) {
+ if (portv[port]->vlanuntag == 0) {
+ tagged = 0;
+ vlan = 0;
+ } else if (packet->header.proto[0] == 0x81 && packet->header.proto[1] == 0x00) {
tagged=1;
vlan=((packet->data[0] << 8) + packet->data[1]) & 0xfff;
if (! ba_check(vlant[vlan].table,port))
--
2.30.2
When compiling the latest master, clang points out that some of your comparisons are tautological:
vxlan.c:108:10: warning: comparison of unsigned expression < 0 is always false [-Wtautological-compare]
if (len < 0)
~~~ ^ ~
vde_autolink.c:865:41: warning: comparison of unsigned expression < 0 is always false [-Wtautological-compare]
if( (curlink->portno = port_reserve()) < 0 ){
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^ ~
wirefilter.c:214:12: warning: comparison of unsigned expression < 0 is always false [-Wtautological-compare]
if (next < 0) next=0;
~~~~ ^ ~
wirefilter.c:1638:35: warning: comparison of unsigned expression < 0 is always false [-Wtautological-compare]
(markovdelay < delay || delay < 0)) delay=markovdelay;
~~~~~ ^ ~
wirefilter.c:1649:38: warning: comparison of unsigned expression < 0 is always false [-Wtautological-compare]
if (speeddelay < delay || delay < 0) delay=speeddelay;
~~~~~ ^ ~
wirefilter.c:1664:39: warning: comparison of unsigned expression < 0 is always false [-Wtautological-compare]
if (speeddelay < delay || delay < 0) delay=speeddelay;
~~~~~ ^ ~
vde_switch's running on Darwin sometime give this error:
send_sockaddr port 1: No buffer space available
And thereby stall gobs of TCP sessions with heavy packetloss. The issue is that PF_UNIX/SOCK_DGRAM sockets on many BSDs will return ENOBUFS when no more data can be accepted via send(). This is regardless of whether its O_NONBLOCK or whether a recent select() returns write-ability.
This can be fixed with the following patch to src/vde_switch/datasock.c. It is probably appropriate on all platforms, especially the *BSDs.
diff --git a/src/vde_switch/datasock.c b/src/vde_switch/datasock.c
index b241383..e66820c 100644
--- a/src/vde_switch/datasock.c
+++ b/src/vde_switch/datasock.c
@@ -15,6 +15,7 @@
#include <stdlib.h>
#include <stdint.h>
#include <libgen.h>
+#include <sched.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
@@ -85,9 +86,13 @@ union request {
static int send_datasock(int fd_ctl, int fd_data, void *packet, int len, int port)
{
+retry:
if (send(fd_data, packet, len, 0) < 0) {
int rv=errno;
- if(rv != EAGAIN && rv != EWOULDBLOCK)
+ if(rv == ENOBUFS) {
+ sched_yield();
+ goto retry;
+ } else if(rv != EAGAIN && rv != EWOULDBLOCK)
printlog(LOG_WARNING,"send_sockaddr port %d: %s",port,strerror(errno));
else
rv=EWOULDBLOCK;
The slow and fast TCP timeouts, as well as timeouts for expiring idle UDP sockets never fire. This results in a file descriptor leak.
It is because times() is used for setting curtime, which only gives 1 second granularity and the fast and slow timeouts are 2 and 500ms, respectively. select() is also called without a timeout.
Attached is a patch that uses clock_gettime(CLOCK_MONOTONIC, ...), modifies the signature of slirp_select_fill() to return a timeval * for select(). I am currently (hackishly) porting this code to Solaris and this makes slirpvde work in my use-case.
diff --git a/src/slirpvde/libslirp.h b/src/slirpvde/libslirp.h
index ecf8e1b..1d95981 100644
--- a/src/slirpvde/libslirp.h
+++ b/src/slirpvde/libslirp.h
@@ -32,7 +32,7 @@ Slirp *slirp_init(int restricted, struct in_addr vnetwork,
struct in_addr vnameserver, void *opaque);
void slirp_cleanup(Slirp *slirp);
-void slirp_select_fill(int *pnfds,
+struct timeval * slirp_select_fill(int *pnfds,
fd_set *readfds, fd_set *writefds, fd_set *xfds);
void slirp_select_poll(fd_set *readfds, fd_set *writefds, fd_set *xfds,
@@ -61,7 +61,7 @@ size_t slirp_socket_can_recv(Slirp *slirp, struct in_addr guest_addr,
#else /* !CONFIG_SLIRP */
-static inline void slirp_select_fill(int *pnfds, fd_set *readfds,
+static inline struct timeval * slirp_select_fill(int *pnfds, fd_set *readfds,
fd_set *writefds, fd_set *xfds) { }
static inline void slirp_select_poll(fd_set *readfds, fd_set *writefds,
diff --git a/src/slirpvde/slirp.c b/src/slirpvde/slirp.c
index 46380a3..dd1c1a2 100644
--- a/src/slirpvde/slirp.c
+++ b/src/slirpvde/slirp.c
@@ -43,6 +43,7 @@ fd_set *global_readfds, *global_writefds, *global_xfds;
u_int curtime;
static u_int time_fasttimo, last_slowtimo;
static int do_slowtimo;
+static struct timespec boottime;
static QTAILQ_HEAD(slirp_instances, Slirp) slirp_instances =
QTAILQ_HEAD_INITIALIZER(slirp_instances);
@@ -191,6 +192,7 @@ static void slirp_init_once(void)
#endif
loopback_addr.s_addr = htonl(INADDR_LOOPBACK);
+ clock_gettime(CLOCK_MONOTONIC, &boottime);
}
//static void slirp_state_save(QEMUFile *f, void *opaque);
@@ -254,15 +256,17 @@ void slirp_cleanup(Slirp *slirp)
#define CONN_CANFRCV(so) (((so)->so_state & (SS_FCANTRCVMORE|SS_ISFCONNECTED)) == SS_ISFCONNECTED)
#define UPD_NFDS(x) if (nfds < (x)) nfds = (x)
-void slirp_select_fill(int *pnfds,
+struct timeval * slirp_select_fill(int *pnfds,
fd_set *readfds, fd_set *writefds, fd_set *xfds)
{
Slirp *slirp;
+ static struct timeval timeo;
+ struct timespec now;
struct socket *so, *so_next;
int nfds;
if (QTAILQ_EMPTY(&slirp_instances)) {
- return;
+ return NULL;
}
/* fail safe */
@@ -270,6 +274,10 @@ void slirp_select_fill(int *pnfds,
global_writefds = NULL;
global_xfds = NULL;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ curtime = (now.tv_sec - boottime.tv_sec) * 1000;
+ curtime += now.tv_nsec / 1000000;
+
nfds = *pnfds;
/*
* First, TCP sockets
@@ -375,6 +383,15 @@ void slirp_select_fill(int *pnfds,
}
*pnfds = nfds;
+ if (time_fasttimo) {
+ timeo.tv_sec = 0;
+ timeo.tv_usec = 2000;
+ return &timeo;
+ } else if (do_slowtimo) {
+ timeo.tv_sec = 0;
+ timeo.tv_usec = 500000;
+ return &timeo;
+ } else return NULL;
}
void slirp_select_poll(fd_set *readfds, fd_set *writefds, fd_set *xfds,
@@ -383,6 +400,7 @@ void slirp_select_poll(fd_set *readfds, fd_set *writefds, fd_set *xfds,
Slirp *slirp;
struct socket *so, *so_next;
int ret;
+ struct timespec now;
if (QTAILQ_EMPTY(&slirp_instances)) {
return;
@@ -392,7 +410,9 @@ void slirp_select_poll(fd_set *readfds, fd_set *writefds, fd_set *xfds,
global_writefds = writefds;
global_xfds = xfds;
- curtime = qemu_get_clock(rt_clock);
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ curtime = (now.tv_sec - boottime.tv_sec) * 1000;
+ curtime += now.tv_nsec / 1000000;
QTAILQ_FOREACH(slirp, &slirp_instances, entry) {
/*
diff --git a/src/slirpvde/slirpvde.c b/src/slirpvde/slirpvde.c
index 8b9584c..aeacaee 100644
--- a/src/slirpvde/slirpvde.c
+++ b/src/slirpvde/slirpvde.c
@@ -522,17 +522,18 @@ int main(int argc, char **argv)
do_redir_x(rx,quiet);
for(;;) {
+ struct timeval *tv;
FD_ZERO(&rs);
FD_ZERO(&ws);
FD_ZERO(&xs);
nfds= -1;
- slirp_select_fill(&nfds,&rs,&ws,&xs);
+ tv = slirp_select_fill(&nfds,&rs,&ws,&xs);
FD_SET(datafd,&rs);
FD_SET(ctlfd,&rs);
if (datafd>nfds) nfds=datafd;
if (ctlfd>nfds) nfds=ctlfd;
- result=select(nfds+1,&rs,&ws,&xs,NULL);
+ result=select(nfds+1,&rs,&ws,&xs,tv);
if (conn != NULL) {
//printf("SELECT %d %d\n",nfds,result);
if (FD_ISSET(datafd,&rs)) {
@@ -543,7 +544,7 @@ int main(int argc, char **argv)
slirp_input(slirp,bufin,nx);
//fprintf(stderr,"TX to slirp %d exit\n",nx);
}
- if (result > 0) {
+ if (result >= 0) {
//fprintf(stderr,"slirp poll\n");
slirp_select_poll(&rs,&ws,&xs,0);
//fprintf(stderr,"slirp poll exit\n");
@@ -560,7 +561,7 @@ int main(int argc, char **argv)
vdestream_recv(vdestream, bufin, nx);
result--;
}
- if (result > 0) {
+ if (result >= 0) {
//fprintf(stderr,"slirp poll\n");
slirp_select_poll(&rs,&ws,&xs,0);
//fprintf(stderr,"slirp poll exit\n");
Hello,
I installed vde package (2.3.2) using brew under Mac OS X Big Sur 11.5.2
I used before vde under Linux, but I tried the same approach with Mac OS without success. The main issue is related to the '-t' option that seems is missing.
sudo vde_switch -t tap0
vde_switch: unrecognized option `-t'
Usage: vde_switch [OPTIONS]
Runs a VDE switch.
(global opts)
-h, --help Display this help and exit
-v, --version Display informations on version and exit
-n --numports Number of ports (default 32)
-x, --hub Make the switch act as a hub
-F, --fstp Activate the fast spanning tree protocol
--macaddr MAC Set the Switch MAC address
--priority N Set the priority for FST (MAC extension)
--hashsize N Hash table size
(opts from datasock module)
-s, --sock SOCK control directory pathname
-s, --vdesock SOCK Same as --sock SOCK
-s, --unix SOCK Same as --sock SOCK
-m, --mode MODE Permissions for the control socket (octal)
--dirmode MODE Permissions for the sockets directory (octal)
-g, --group GROUP Group owner for comm sockets
(opts from consmgmt module)
-d, --daemon Daemonize vde_switch once run
-p, --pidfile PIDFILE Write pid of daemon to PIDFILE
-f, --rcfile Configuration file (overrides /etc/vde2/vde_switch.rc and ~/.vderc)
-M, --mgmt SOCK path of the management UNIX socket
--mgmtmode MODE management UNIX socket access mode (octal)
--mgmtgroup GROUP management UNIX socket group name
Report bugs to [email protected]
If I run also a man vde_switch, I have
vde_switch [ -hub ] [ -sock commdirpath ] [ -mod octal-mode ] [ -group NAME ] [ -tap interface ] [ -daemon ]
but even if I try with '-tap', I have the same error.
Please, can you help me?
thanks
Fausto
Vde_router is absent , why ? I can't use Virtualbricks without it. Is there a way to restore it ?
In older versions of vde2 the following switch startup configuration worked like a charm:
vlan/create 1 # isolated w/o real network access
vlan/create 2 # access to local samba server
vlan/create 3 # bridged to hypervisor
port/setvlan 1 1
port/setvlan 2 3
port/create 3
port/setvlan 3 2
vlan/remove 0
In other words, I set up 3 new VLANs and then removed the default VLAN # 0.
I did this because this default VLAN seemed to be special in that it was exempt from VLAN filtering and could therefore see all the traffic. I considered that a security risk and therefore deleted the VLAN.
However, current versions of vde_switch crash when adding new ports after VLAN # 0 has been removed.
Core dump analysis shows the crash happens in line 170 of file src/vde_switch/port.c where the expression
ba_set(vlant[0].table,i)
is evaluated with vlant[0].table containing a null pointer.
Suggested course of action: Either the code should check whether vlant[0].table contains a null pointer and then avoid dereferencing it, or removal of VLAN # 0 should be forbidden because then there will not be a null pointer.
Hello,
I am researching virtual routers and switches for possible use in a P2P mesh relay network system and would like to read more documentation on the VDE project although it seems a bit dated.
Would be interested in how well it scales and typical throughputs that it can achieve.
Is there any documentation that I could read?
Thanks
configure ignores these options and enables them anyway.
VDE 2.3.3
When compiling the latest master, clang pointed out that some variables are unused:
cryptcab.c:94:6: warning: unused variable 'tlen' [-Wunused-variable]
int tlen, olen;
^
cryptcab.c:94:12: warning: unused variable 'olen' [-Wunused-variable]
int tlen, olen;
^
When trying to compile the latest master, my clang compiler pointed out this typo in a header guard:
In file included from pfifo.c:14:
./vde_l3.h:1:9: warning: '_VDE_L3_H_' is used as a header guard here, followed by #define of a different macro [-Wheader-guard]
#ifndef _VDE_L3_H_
^~~~~~~~~~
./vde_l3.h:2:9: note: '_VDE_L3_H__' is defined here; did you mean '_VDE_L3_H_'?
#define _VDE_L3_H__
^~~~~~~~~~~
_VDE_L3_H_
1 warning generated.
Hi,
in the FreeBSD port net/vde2
, file aclocal.m4
, line 861 says:
[am_cv_python_version=`$PYTHON -c "import sys; sys.stdout.write(sys.version[[:3]])"`])
This does no longer work in Python 3.10; it will yield "3.1"
.
A possible fix:
python3.10 -c "import sys; sys.stdout.write('.'.join([str(m) for m in sys.version_info[0:2]]))"
In case this problem does concern FreeBSD rather than your
project, please report it yourself. I will no longer report
any problems to the FreeBSD project since some of my reports
were totally ignored, some even for years.
Greetings,
Bertram
I appears that the vlancreate() function in port.c which is called on the management command "vlan/create" should be valid for a vlan id of 4094. However the if statement checking for validity excludes 4094 "vlan < NUMOFVLAN-1". It also seems that vlanaddport() is afflicted with the same issue with it's vlan id check.
Hello,
From our testing, it seems that packets bigger than 1504 are dropped by vde_switch.
Simply updating the size of the data in src/vde_switch/port.h to a higher value seems to fix the issue (we put 16384 ourselves instead of 1504).
Before:
root@dut:~# ping 1.1.1.1 -s 1476
PING 1.1.1.1 (1.1.1.1) 1476(1504) bytes of data.
1484 bytes from 1.1.1.1: icmp_seq=1 ttl=64 time=1.44 ms
^C
--- 1.1.1.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.447/1.447/1.447/0.000 ms
root@dut:~# ping 1.1.1.1 -s 1478
PING 1.1.1.1 (1.1.1.1) 1478(1506) bytes of data.
^C
--- 1.1.1.1 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
After the patch, packets are received on the other side.
Could you raise the value, or do you want me to contribute a patch with a higher value?
If I were to contribute, should the value in src/vde_vxlan/vxlan.h be updated as well?
I attached the patch for reference:
0001-add-support-for-jumbo.patch.txt
Regards,
Samuel
We use this software intensively and lack it on Windows
The latest release v2.3.2 was nearly 10 years ago: https://sourceforge.net/projects/vde/files/vde2/2.3.2/
@rd235 @danielinux
Could you consider creating a new release from the current master?
(I know vdeplug4 is the successor of this project, but homebrew package of QEMU still depends on VDE2, and my "vde_vmnet" requires vde_switch
of VDE2)
This is the error:
cryptcab.c:25:23: error: storage size of ‘ctx’ isn’t known
static EVP_CIPHER_CTX ctx
^~~
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.