GithubHelp home page GithubHelp logo

rfc4960bis's People

Contributors

stewrtrs avatar tuexen avatar

Stargazers

 avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

rfc4960bis's Issues

Issue with Transmittal in Fast Recovery


Old text (Section 7.2.4):

  1. Determine how many of the earliest (i.e., lowest TSN) DATA chunks marked for retransmission will fit into a single packet, subject to constraint of the path MTU of the destination transport address to which the packet is being sent. Call this value K. Retransmit those K DATA chunks in a single packet. When a Fast Retransmit is being performed, the sender SHOULD ignore the value of CWND and SHOULD NOT delay retransmission for this single packet.

New text (Section 7.2.4):

  1. If not in Fast Recovery, determine how many of the earliest (i.e., lowest TSN) DATA chunks marked for retransmission will fit into a single packet, subject to constraint of the path MTU of the destination transport address to which the packet is being sent. Call this value K. Retransmit those K DATA chunks in a single packet. When this Fast Retransmit is being performed, the sender SHOULD ignore the value of CWND and SHOULD NOT delay retransmission for this single packet.

Issue with partial_bytes_acked after T3-rtx Expiration


Old text (Section 7.2.3):

When the T3-rtx timer expires on an address, SCTP should perform slow start by:

ssthresh = max(cwnd/2, 4_MTU)
cwnd = 1_MTU


New text (Section 7.2.3):

When the T3-rtx timer expires on an address, SCTP should perform slow start by:

ssthresh = max(cwnd/2, 4_MTU)
cwnd = 1_MTU
partial_bytes_acked = 0

Initial Value of ssthresh

5.1 Description of the Problem:

The small value of ssthresh dramatically impacts SCTP throughput. Setting of initial ssthresh to first advertised RWND is vulnerable to subsequent increase of RWND, i.e. if initially receiver RWND is set to a small value then the slow start phase of SCTP Congestion Control will be short and this will impact throughput and, even if later on RWND is increased, the slow start phase will not be prolonged.

TCP RFC5681 addresses the issue:
"The initial value of ssthresh SHOULD be set arbitrarily high (e.g.,to the size of the largest possible advertised window), but ssthresh MUST be reduced in response to congestion. Setting ssthresh as high as possible allows the network conditions, rather than some arbitrary host limit, to dictate the sending rate. In cases where the end systems have a solid understanding of the network path, more carefully setting the initial ssthresh value may have merit (e.g., such that the end host does not create congestion along the path)."

5.2 Text Changes to the Document:


Old text (Section 7.2.1):

The initial value of ssthresh MAY be arbitrarily high (for example, implementations MAY use the size of the receiver advertised window).


New text (Section 7.2.1):

The initial value of ssthresh SHOULD be set arbitrarily high (for example, to the size of the largest possible advertised window).

Done!

Change Request - Misinterpretation of INIT re-transmission behaviour

Our understanding is that RFC4960 specifies the following process:

The Upper Layer Protocol (ULP) (on host A) requests an SCTP association to host B’s (single) IP address (destination transport address) via the ASSOCIATE primitive to its local previously initialised SCTP instance. The ASSOCIATE primitive requests SCTP to set up an SCTP association to a single destination transport address (Note: the ASSOCIATE primitive request can only contain a single destination transport address). The ASSOCIATE primitive response to the ULP may include a list of further IP addresses which are learned during the SCTP association establishment phase.

The ASSOCIATE primitive request triggers the sending of an INIT chunk. If no response to the INIT is received before timer T1-init expires, then the INIT is re-sent to the same destination transport address, continuing until Max.Init.Retansmits limit has been reached. At this point the failure is reported to the Upper layer protocol, which may then send another ASSOCIATE primitive request containing an alternative destination transport address.

We believe this is the correct way of interpreting the RFC. However, there appear to be some who have assumed that the ASSOCIATE primitive request can be provided with (say) two (or more) destination addresses, and if the timer awaiting a response to the first INIT chunk expires then it re-tries by retransmitting the INIT chunk to the other destination address.

Supporting quotes for our interpretation:

RFC 4960 Section 10.1

B) Associate

  Format: ASSOCIATE(local SCTP instance name,
          destination transport addr, outbound stream count)
  -> association id [,destination transport addr list]
        [,outbound stream count]

and

• destination transport addr - specified as one of the transport addresses of the peer endpoint with which the association is to be established.

RFC 4960 Section 4 Note 2

If the T1-init timer expires, the endpoint MUST retransmit INIT
and restart the T1-init timer without changing state. This MUST
be repeated up to 'Max.Init.Retransmits' times. After that, the
endpoint MUST abort the initialization process and report the
error to the SCTP user.

The change request for RFC 4960 is to clarify that this interpretation is the correct, and only possible one.

Use simple present

Replace

Upon reception of the COOKIE ECHO chunk, endpoint "Z" will reply with a COOKIE ACK chunk after building a TCB and moving to the ESTABLISHED state.

by

Upon reception of the COOKIE ECHO chunk, endpoint "Z" replies with a COOKIE ACK chunk after building a TCB and moving to the ESTABLISHED state.

and

Upon reception of the COOKIE ACK, endpoint "A" will move from the COOKIE-ECHOED state to the ESTABLISHED state, stopping the T1-cookie timer.

by

Upon reception of the COOKIE ACK, endpoint "A" moves from the COOKIE-ECHOED state to the ESTABLISHED state, stopping the T1-cookie timer.

Space after hyphen

There are many places like multi- homing or T2- shutdown where the space has been added after -.

ICMP9 Procedure of ICMP Handling

Old:
ICMP9) If the ICMPv6 code is "Destination Unreachable",
New:
ICMP9) If the ICMPv6 type is "Destination Unreachable" or the ICMPv4 type is 
"Destination Unreachable" except for "Protocol Unreachable" and
"Fragmentation Needed" codes,

Sort of the above, but be consistent with ICMP8)
If the ICMP code is an "Unrecognized Next Header Type
Encountered" or a "Protocol Unreachable",

Issue: ICMP4 not covered.

Path for Fast Retransmission


Old text (Section 6.4):

Furthermore, when its peer is multi-homed, an endpoint SHOULD try to retransmit a chunk that timed out to an active destination transport address that is different from the last destination address to which the DATA chunk was sent.


New text (Section 6.4):

Furthermore, when its peer is multi-homed, an endpoint SHOULD try to retransmit a chunk that timed out to an active destination transport address that is different from the last destination address to which the DATA chunk was sent.
When its peer is multi-homed, an endpoint SHOULD send fast retransmissions to the path where original data was sent to. If the the primary path has changed and it was sent there before the fast retransmit the implementation MAY send it to this new primary path.

Done

Issue with cwnd Degradation due to Max.Burst

4.1 Description of the Problem:

Some implementations were having degradation of cwnd because of the Max.Burst limit. This was due to misinterpretation of the suggestion in RFC4960, Section 6.1, how to use the Max.Burst parameter when calculating the number of packets to transmit.

Another related issue with Max.Burst is when to apply the limit. The example in the RFC refers to the output routine while it may be called for each send request from the ULP. Following this approach from the example, SCTP may aggressively send data. On the other hand it was intended that the limit should be applied before the arrival of the SACK but the RFC does not specify it.

4.2 Text Changes to the Document:


Old text (Section 6.1):

D) When the time comes for the sender to transmit new DATA chunks, the protocol parameter Max.Burst SHOULD be used to limit the number of packets sent. The limit MAY be applied by adjusting cwnd as follows:

if ((flightsize + Max.Burst*MTU) < cwnd)
    cwnd = flightsize + Max.Burst*MTU

Or it MAY be applied by strictly limiting the number of packets emitted by the output routine.


New text (Section 6.1):

D) When the time comes for the sender to transmit new DATA chunks, the protocol parameter Max.Burst SHOULD be used to limit the number of packets sent. The limit MAY be applied by adjusting cwnd as follows:

if((flightsize + Max.Burst*MTU) < cwnd) 
    cwnd = flightsize + Max.Burst*MTU

Or it MAY be applied by strictly limiting the number of packets emitted by the output routine.
When calculating the number of packets to transmit and particularly using the formula above, cwnd SHOULD NOT be changed.

Only one packet after T3


Old text (Section 7.2.1):

o The initial cwnd after a retransmission timeout MUST be no more than 1*MTU.


New text (Section 7.2.1):

o The initial cwnd after a retransmission timeout MUST be no more than 1*MTU and only one packet is allowed to be sent in flight until successful acknowledgement.

INIT ACK Path for INIT in COOKIE-WAIT State


Old text (Section 5.2.1):

Upon receipt of an INIT in the COOKIE-WAIT state, an endpoint MUST respond with an INIT ACK using the same parameters it sent in its original INIT chunk (including its Initiate Tag, unchanged). When responding, the endpoint MUST send the INIT ACK back to the same address that the original INIT (sent by this endpoint) was sent.


New text (Section 5.2.1):

Upon receipt of an INIT in the COOKIE-WAIT state, an endpoint MUST respond with an INIT ACK using the same parameters it sent in its original INIT chunk (including its Initiate Tag, unchanged). When responding, the following rules MUST be applied:

  1. The INIT-ACK MUST only be sent to an address configured by the upper layer.

  2. The INIT-ACK MUST only be sent to an address reported in the incoming INIT.

  3. The INIT-ACK SHOULD be sent to the source address of the received INIT.

Done

Outstanding Data, Flightsize and Data In Flight Terms


Old text (Section 1.3):

o Congestion window (CWND): An SCTP variable that limits the data, in number of bytes, a sender can send to a particular destination transport address before receiving an acknowledgement.

o Outstanding TSN (at an SCTP endpoint): A TSN (and the associated DATA chunk) that has been sent by the endpoint but for which it has not yet received an acknowledgement.


New text (Section 1.3):

o Outstanding TSN (at an SCTP endpoint): A TSN (and the associated DATA chunk) that has been sent by the endpoint but for which it has not yet received an acknowledgement.

o Outstanding data (or Data in flight): DATA chunks associated with outstanding TSNs. A retransmitted DATA chunk is counted once in outstanding data. A DATA chunk which is classified is lost but which has not been retransmitted yet is not in outstanding data.

o Flightsize: The amount of bytes of outstanding data to a particular destination transport address at any given time.

o Congestion window (CWND): An SCTP variable that limits outstanding data, in number of bytes, a sender can send to a particular destination transport address before receiving an acknowledgement.

Circumventing SWS without bundling of outgoing DATA chunks?

Rule A) of Section 6.1. states that "The sender MUST also have an algorithm for sending new DATA chunks to avoid silly window syndrome (SWS) as described in [RFC0813]. The algorithm can be similar to the one described in Section 4.2.3.4 of [RFC1122]." However, Section 6. states that "The fragmentation and bundling mechanisms, as detailed in Section 6.9 and Section 6.10, are OPTIONAL to implement by the data sender".

Which mechanisms can be used to solve the SWS in an implementation that does not implement DATA chunks bundling? A Nagle-like algorithm as recommended does not seem to fit the bill as it is based on buffering up to 1 MSS before sending. SCTP being message-oriented, it sounds like we would need to bundle DATA chunks into a single packet to avoid SWS.

Order of Adjustments of partial_bytes_acked and cwnd


Old text (Section 7.2.2):

o When partial_bytes_acked is equal to or greater than cwnd and before the arrival of the SACK the sender had cwnd or more bytes of data outstanding (i.e., before arrival of the SACK, flightsize was greater than or equal to cwnd), increase cwnd by MTU, and reset partial_bytes_acked to (partial_bytes_acked - cwnd).


New text (Section 7.2.2):

o When partial_bytes_acked is equal to or greater than cwnd and before the arrival of the SACK the sender had CWND or more bytes of data outstanding (i.e., before arrival of the SACK, flightsize was greater than or equal to cwnd), partial_bytes_acked is reset to min(partial_bytes_acked - cwnd, cwnd).

OK.

Don't require SACK; ERROR, allows also ERROR; SACK.

Replace in Section 6.5

Every DATA chunk MUST carry a valid stream identifier.  If an
endpoint receives a DATA chunk with an invalid stream identifier, it
shall acknowledge the reception of the DATA chunk following the
normal procedure, immediately send an ERROR chunk with cause set to
"Invalid Stream Identifier" (see Section 3.3.10), and discard the
DATA chunk.  The endpoint may bundle the ERROR chunk in the same
packet as the SACK as long as the ERROR follows the SACK.

by

Every DATA chunk MUST carry a valid stream identifier.  If an
endpoint receives a DATA chunk with an invalid stream identifier, it
shall acknowledge the reception of the DATA chunk following the
normal procedure, immediately send an ERROR chunk with cause set to
"Invalid Stream Identifier" (see Section 3.3.10), and discard the
DATA chunk.  The endpoint may bundle the ERROR chunk in the same
packet as the SACK.

ssthresh Refresh after Idle Period

Old text (Section 7.2.1):

o The initial cwnd before DATA transmission or after a sufficiently
long idle period MUST be set to min(4_MTU, max (2_MTU, 4380
bytes)).


New text (Section 7.2.1):

o The initial cwnd before DATA transmission MUST be set to min(4_MTU, max (2_MTU, 4380
bytes)).

Old text (Section 7.2.1):

o When the endpoint does not transmit data on a given transport address, the cwnd of the transport address should be adjusted to max(cwnd/2, 4*MTU) per RTO.


New text (Section 7.2.1):

o When the endpoint does not transmit data on a given transport address, the cwnd of the transport address should be adjusted to max(cwnd/2, 4*MTU) per RTO . At first reduction, store the cwnd in ssthresh.

Zero Window Probing when the peer's rwnd > 0

The RFC says:

A) At any given time, the data sender MUST NOT transmit new data to
any destination transport address if its peer's rwnd indicates
that the peer has no buffer space (i.e., rwnd is 0
; see Section
6.2.1). However, regardless of the value of rwnd (including if it
is 0), the data sender can always have one DATA chunk in flight to
the receiver if allowed by cwnd (see rule B, below). This rule
allows the sender to probe for a change in rwnd that the sender
missed due to the SACK's having been lost in transit from the data
receiver to the data sender.
When the receiver's advertised window is zero, this probe is
called a zero window probe. Note that a zero window probe SHOULD
only be sent when all outstanding DATA chunks have been
cumulatively acknowledged and no DATA chunks are in flight. Zero
window probing MUST be supported.

I believe the intention of the RFC is to start zero window probing when the size of the next DATA chunk to be sent is more than available space in the peer's buffer, i.e. rwnd might be more than 0.

Should it be clarified?

Issue with Automatically CONFIRMED Addresses

Deal with the case that the addresses (plural!) reported by the upper layer does NOT match what the peer reports.

This covers

Old text (Section 5.4):

  1. Any address passed to the sender of the INIT by its upper layer is automatically considered to be CONFIRMED.

New text (Section 5.4):

  1. Any address passed to the sender of the INIT by its upper layer in the ASSOCIATE primitive is automatically considered to be CONFIRMED.

But we don't take the text.

partial_bytes_acked Increase


Old text (Section 7.2.2):

o Whenever cwnd is greater than ssthresh, upon each SACK arrival that advances the Cumulative TSN Ack Point, increase partial_bytes_acked by the total number of bytes of all new chunks acknowledged in that SACK including chunks acknowledged by the new Cumulative TSN Ack and by Gap Ack Blocks.


New text (Section 7.2.2):

o Whenever cwnd is greater than ssthresh, upon each SACK arrival, increase partial_bytes_acked by the total number of bytes of all new chunks acknowledged in that SACK including chunks acknowledged by the new Cumulative TSN Ack and by Gap Ack Blocks and by number of bytes of duplicated chunks reported in Duplicate TSNs.

cwnd overbooking

You must be allowed to have up to cwnd+PMTU-1 byte outstanding. Only one packet!

Something like

Old text (Section 6.1):

B) At any given time, the sender MUST NOT transmit new data to a given transport address if it has cwnd or more bytes of data outstanding to that transport address.


New text (Section 6.1):

B) At any given time, the sender MUST NOT transmit new data to a given transport address if it has cwnd or more bytes of outstanding data to that transport address. If data is available the sender should exceed cwnd by up to (PMTU-1) bytes on a new data transmission if the flightsize does not currently reach cwnd. The breach of cwnd MUST constitute one packet only.

However, this might no be the correct place.

Update CRC32c code

Wouldn't it make sense to use types like uint8_t, uint32_t and so on instead of long and unsigned long? At least on FreeBSD we use the platform independent types on various platforms.

Correctness of comment from blob/master/example.c

In other words, the bytes of each bit are in the right order, but the bytes have been byteswapped. So we now do an explicit

Is this comment correct in that the bytes belonging to each bit are in the right order?

Shouldn't it read "bits of each byte" ?

Inconsistency in Sections 8.2 and 8.3

RFC4960, Section 8.2, says that when a destination address becomes inactive due to an unacknowledged DATA or HEARTBEAT, SCTP SHOULD send a notification to the upper layer while Section 8.3 says that when a destination address becomes inactive due to an unacknowledged HEARTBEAT, SCTP may send a notification to the upper layer. This makes the text inconsistent.

The proposal is to use SHOULD also in Section 8.3.

Issues with Action B of Table 2

14.1 Description of the Problem:

Two issues have been found with the description of Action B of Table 2:

  • The text assumes that when this initialization collision use case happens the endpoint may already be in the ESTABLISHED state while it is impossible.
  • The text does not cover the second row of Action B in the table when Peerís Tag is 0.

14.2 Text Changes to the Document:


Old text (Section 5.2.1):

B) In this case, both sides may be attempting to start an association at about the same time, but the peer endpoint started its INIT after responding to the local endpoint's INIT. Thus, it may have picked a new Verification Tag, not being aware of the previous tag it had sent this endpoint. The endpoint should stay in or enter the ESTABLISHED state, but it MUST update its peer's Verification Tag from the State Cookie, stop any init or cookie timers that may be running, and send a COOKIE ACK.


New text (Section 5.2.1):

Bí) In this case, both sides may be attempting to start an association at about the same time, but the peer endpoint started its INIT after responding to the local endpoint's INIT. Thus, it may have picked a new Verification Tag, not being aware of the previous tag it had sent this endpoint. The endpoint should enter the ESTABLISHED state and MUST update its peer's Verification Tag from the State Cookie, stop any init or cookie timers that may be running, and send a COOKIE ACK.

Bíí) In this case, both sides may be attempting to start an association at about the same time and both sides received INIT in the COOKIE-WAIT state, but INIT from the peer is lost or received later than COOKIE-ECHO. Thus, the peerís Verification Tag is unknown at COOKIE ECHO receiving. The endpoint should enter the ESTABLISHED state and MUST update its peer's Verification Tag from the State Cookie, stop any init or cookie timers that may be running, and send a COOKIE ACK.

14.3 Solution Description:

The NEW text does not assume a situation when the endpoint is already in the ESTABLISHED state.

The Action B is split into two actions to address different scenarios.

14.4 Note:

It is proposed that B is split into two different actions like Bí and Bíí above but in the RFC it should become B and C so that current C and D needs to be shifted.

Discuss via e-mail.

cwnd increase by more than MTU per RTT

The RFC does not clearly states that SCTP must not increase cwnd by more than one MTU per RTT. The text in Section 7.2.2 should be updated.
It should be decided what to do with partial_bytes_acked when it becomes equal or higher than cwnd but cwnd is not increased (either due to the condition above or due to flightsize which is less than cwnd).

Removal of port number registration request

Shouldn't the following text be removed?

This document registers the following ports.  (These registrations
should be considered models to follow for future allocation
requests.)

      discard    9/sctp  Discard  # IETF TSVWG
                                  # Randall Stewart <[email protected]>
                                  # [RFC4960]

         The discard service, which accepts SCTP connections on port
         9, discards all incoming application data and sends no data
         in response.  Thus, SCTP's discard port is analogous to
         TCP's discard port, and might be used to check the health
         of an SCTP stack.

      ftp-data  20/sctp  FTP      # IETF TSVWG
                                  # Randall Stewart <[email protected]>
                                  # [RFC4960]

      ftp       21/sctp  FTP      # IETF TSVWG
                                  # Randall Stewart <[email protected]>
                                  # [RFC4960]

         File Transfer Protocol (FTP) data (20) and control ports
         (21).

      ssh       22/sctp  SSH      # IETF TSVWG
                                  # Randall Stewart <[email protected]>
                                  # [RFC4960]

         The Secure Shell (SSH) remote login service, which allows
         secure shell logins to a host.

      http      80/sctp  HTTP     # IETF TSVWG
                                  # Randall Stewart <[email protected]>
                                  # [RFC4960]

         World Wide Web HTTP over SCTP.

      bgp      179/sctp  BGP      # IETF TSVWG
                                  # Randall Stewart <[email protected]>
                                  # [RFC4960]

         Border Gateway Protocol over SCTP.

      https    443/sctp  HTTPS    # IETF TSVWG
                                  # Randall Stewart <[email protected]>
                                  # [RFC4960]

         World Wide Web HTTP over TLS/SSL over SCTP.

HB ACK and the association error counter

RFC4960 in Section 8.3 prescribes to clear the association error counter when HB ACK is received and 'must' is used for it. Following this rule blindly may result in problems in some specific use cases like when a router discards DATA/SACK but not HB/HB ACK. It is proposed to change the text so that it uses SHOULD and then explains the reasons of MAY why not to clear the association error counter.

RFC4960 already uses SHOULD/should for clearing the destination error counter when HB ACK is received. It may also be beneficial to explain the reasons of MAY why not to clear the destination error counter.

TSN used before defined

The first use of the acronym TSN is on page 7.

Cumulative TSN Ack Point: The TSN of the last DATA chunk
acknowledged via the Cumulative TSN Ack field of a SACK.

TSN isn't defined until page 8.

SCTP association: A protocol relationship between SCTP endpoints,
composed of the two SCTP endpoints and protocol state information
including Verification Tags and the currently active set of
Transmission Sequence Numbers (TSNs), etc. An association can be
uniquely identified by the transport addresses used by the
endpoints in the association. Two SCTP endpoints MUST NOT have
more than one SCTP association between them at any given time.

Issue with Zero Window Probing and Unreachable Primary Path


Old text (Section 6.1):

If the sender continues to receive new packets from the receiver while doing zero window probing, the unacknowledged window probes should not increment the error counter for the association or any destination transport address. This is because the receiver MAY keep its window closed for an indefinite time. Refer to Section 6.2 on the receiver behavior when it advertises a zero window.


New text (Section 6.1):

If the sender continues to receive SACKs from the receiver while doing zero window probing, the unacknowledged window probes should clear the error counter for the association or any destination transport address. This is because the receiver MAY keep its window closed for an indefinite time. Refer to Section 6.2 on the receiver behavior when it advertises a zero window.

Done.

Issue with Value 0 in Table 2

Table 2 of RFC4960 has value 0 of Peerís Tag in row with Action B while value 0 is explained in Legend as No Tie-Tag in cookie (unknown).

13.2 Text Changes to the Document:


Old text (Section 5.2.4):

Legend:
X - Tag does not match the existing TCB.
M - Tag matches the existing TCB.
0 - No Tie-Tag in cookie (unknown).
A - All cases, i.e., M, X, or 0.


New text (Section 5.2.4):

Legend:
X - Tag does not match the existing TCB.
M - Tag matches the existing TCB.
0 - No Tag in cookie (unknown).
A - All cases, i.e., M, X, or 0.

13.3 Solution Description:

The NEW text uses Tag in the explanation of value 0 that refers to any Tag of the table (Local Tag, Peerís Tag, Local-Tie-Tag, Peerís-Tie-Tag).

Discuss via e-mail. Only a typo?

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.