Technical Analysis of BambuLab's X1C Network Traffic

Technical analysis of BambuLab's network under different operating modes (LAN Only, Cloud, and Offline).

Technical Analysis of BambuLab's X1C Network Traffic

Introduction

Lately, there's been some discussion around what BambuLab's 3D Printers send over the network in various modes - however, I haven't seen any proof or analysis of the network traffic, so I decided to look at it myself using Wireshark and to publish my findings. BambuLab has also openly called out for any proof to be presented in the latest blog post https://blog.bambulab.com/setting-the-record-straight/

I am not a professional security researcher but rather a software engineer with over a decade of experience, but I still believe that my findings are apparent in dispelling some of the claims, as well as providing evidence for such findings.

I talk a bit more about the setup and go into technical details below, but the tl;dr is:

In LAN only mode the printer does not send any information to any outside servers, but it does get time information from ntp.org. Even if a print is marked as failed and "Submit and Close" is clicked nothing is sent.
In offline mode the printer does not attempt to "secretly" connect to any known or open networks, it stays offline.
In Cloud/Internet mode the printer is not sending any large quantities of data except the camera stream, and camera stream is only sent when there are clients using it. Camera stream is sent directly to devices, if possible, and not to 3rd party servers.
Changing from one mode to another doesn't cause any unusual changes in the traffic, so the printer isn't "suddenly sending everything" when it goes from LAN/Offline mode to Online mode.

Since BambuLab is known to provide units for reviews etc. I think it's also worth mentioning that I am in no way shape or form affiliated with BambuLab or any other party, private or otherwise, that has to do anything with any 3D printers whatsoever. I do own X1C, but I have purchased it myself about a year ago with my own money. There are no affiliate links, there are no ads, I am not promoting anything, and I do not otherwise profit from this post.

Setup

The setup is pretty straightforward. I have created an access point, and I have Wireshark logging all the data that gets sent over it. This does not allow for an inspection of encrypted/SSL data that gets sent, but things like the packet size, IPs, etc. are visible to me. The benefit of this approach is that the printer does not know whether its traffic is being watched or not, the downside is that not every single detail is visible. To inspect every last detail, it would also require installing different certificates on the server, which isn't possible, and it would also be obvious to the printer.

To illustrate roughly how it looks like, it's something like this:

Diagram showing BambuLab X1C connecting to a Wi-Fi Access Point before connecting to the cloud, and Wireshark in the middle displaying what requests are being done between the two and on the local network

Firmware version tested: 01.07.00.00. I explicitly did not update to the latest version that released a few days after the allegations that LAN mode isn't to be trusted.

What isn't covered in this article

I am not analyzing the traffic that the mobile App does, I am not analyzing what the BambuStudio does. I did observe traffic with those launched and what kind of effect it might have, but I haven't looked into what they send. If there's enough interest, I might take a look into that as well.

I, of course, did not have this network monitoring running for a year or longer, I only observed traffic anywhere from a few minutes (e.g. to analyze what it does when it boots) to a few days to see how it behaves when idle.

Offline Mode

Let's start with an easy one.

While in offline mode, X1C did not connect to any Wi-Fi networks that it had knowledge (password) of, nor did it connect to any open networks. I set up a new AP on a different device and left the network open - the printer did not connect to it.

LAN Only Mode

Boot

When the printer boots, the LAN mode is overall silent; however, there is one thing that's done that goes outside the local network, namely NTP.

The Network Time Protocol is a networking protocol for clock synchronization between computer systems over packet-switched, variable-latency data networks. In operation since before 1985, NTP is one of the oldest Internet protocols in current use.

X1C uses ntp.org for NTP time sync, it makes the DNS request to get the IP of {0,1,2,3}.pool.ntp.org

Frame 31: 74 bytes on wire (592 bits), 74 bytes captured (592 bits) on interface \Device\NPF_{A143A799-204A-4470-8CA9-DC91CA7DBE5E}, id 0
Ethernet II, Src: AMPAKTechnol_41:5d:1e (b8:13:32:41:5d:1e), Dst: aa:6d:aa:9e:f5:34 (aa:6d:aa:9e:f5:34)
Internet Protocol Version 4, Src: 192.168.137.49, Dst: 192.168.137.1
User Datagram Protocol, Src Port: 47181, Dst Port: 53
    Source Port: 47181
    Destination Port: 53
    Length: 40
    Checksum: 0xd552 [unverified]
    [Checksum Status: Unverified]
    [Stream index: 4]
    [Timestamps]
    UDP payload (32 bytes)
Domain Name System (query)
    Transaction ID: 0x4743
    Flags: 0x0100 Standard query
    Questions: 1
    Answer RRs: 0
    Authority RRs: 0
    Additional RRs: 0
    Queries
        1.pool.ntp.org: type A, class IN
            Name: 1.pool.ntp.org
            [Name Length: 14]
            [Label Count: 4]
            Type: A (1) (Host Address)
            Class: IN (0x0001)
    [Response In: 33]

Once it has the IPs, it queries them for the time, this request looks like this:

Frame 55: 90 bytes on wire (720 bits), 90 bytes captured (720 bits) on interface \Device\NPF_{A143A799-204A-4470-8CA9-DC91CA7DBE5E}, id 0
Ethernet II, Src: AMPAKTechnol_41:5d:1e (b8:13:32:41:5d:1e), Dst: aa:6d:aa:9e:f5:34 (aa:6d:aa:9e:f5:34)
Internet Protocol Version 4, Src: 192.168.137.49, Dst: 176.9.157.155
User Datagram Protocol, Src Port: 123, Dst Port: 123
    Source Port: 123
    Destination Port: 123
    Length: 56
    Checksum: 0x98a0 [unverified]
    [Checksum Status: Unverified]
    [Stream index: 8]
    [Timestamps]
    UDP payload (48 bytes)
Network Time Protocol (NTP Version 4, client)
    Flags: 0xe3, Leap Indicator: unknown (clock unsynchronized), Version number: NTP Version 4, Mode: client
    [Response In: 59]
    Peer Clock Stratum: unspecified or invalid (0)
    Peer Polling Interval: 6 (64 seconds)
    Peer Clock Precision: -20 (0.000000954 seconds)
    Root Delay: 0.000000 seconds
    Root Dispersion: 0.000168 seconds
    Reference ID: The association has not yet synchronized for the first time
    Reference Timestamp: NULL
    Origin Timestamp: NULL
    Receive Timestamp: NULL
    Transmit Timestamp: Dec 23, 2023 08:37:50.657144875 UTC

And it receives a response with the time:

Frame 58: 90 bytes on wire (720 bits), 90 bytes captured (720 bits) on interface \Device\NPF_{A143A799-204A-4470-8CA9-DC91CA7DBE5E}, id 0
Ethernet II, Src: aa:6d:aa:9e:f5:34 (aa:6d:aa:9e:f5:34), Dst: AMPAKTechnol_41:5d:1e (b8:13:32:41:5d:1e)
Internet Protocol Version 4, Src: 158.101.188.125, Dst: 192.168.137.49
User Datagram Protocol, Src Port: 123, Dst Port: 123
    Source Port: 123
    Destination Port: 123
    Length: 56
    Checksum: 0x33d0 [unverified]
    [Checksum Status: Unverified]
    [Stream index: 9]
    [Timestamps]
    UDP payload (48 bytes)
Network Time Protocol (NTP Version 4, server)
    Flags: 0x24, Leap Indicator: no warning, Version number: NTP Version 4, Mode: server
    [Request In: 56]
    [Delta Time: 0.013601000 seconds]
    Peer Clock Stratum: secondary reference (2)
    Peer Polling Interval: 6 (64 seconds)
    Peer Clock Precision: -18 (0.000003815 seconds)
    Root Delay: 0.004318 seconds
    Root Dispersion: 0.000015 seconds
    Reference ID: 129.69.1.153
    Reference Timestamp: NULL
    Origin Timestamp: Dec 23, 2023 08:37:50.657651530 UTC
    Receive Timestamp: Dec 23, 2023 08:39:21.009648849 UTC
    Transmit Timestamp: Dec 23, 2023 08:39:21.009697569 UTC

The device is pretty chatty with the NTP at the boot, probably unnecessarily so - but it never queried anything other than pool.ntp.org and never queried anything other than the time.

Beyond the NTP, the printer advertises itself to other local devices like this. This data is sent to 255.255.255.255 meaning that local network only devices can listen to it - this is not a request over the internet.

Frame 132: 470 bytes on wire (3760 bits), 470 bytes captured (3760 bits) on interface \Device\NPF_{A143A799-204A-4470-8CA9-DC91CA7DBE5E}, id 0
Ethernet II, Src: AMPAKTechnol_41:5d:1e (b8:13:32:41:5d:1e), Dst: Broadcast (ff:ff:ff:ff:ff:ff)
Internet Protocol Version 4, Src: 192.168.137.49, Dst: 255.255.255.255
User Datagram Protocol, Src Port: 2021, Dst Port: 2021
    Source Port: 2021
    Destination Port: 2021
    Length: 436
    Checksum: 0xab34 [unverified]
    [Checksum Status: Unverified]
    [Stream index: 11]
    [Timestamps]
    UDP payload (428 bytes)
Data (428 bytes)
    Data [truncated]: 4e4f54494659202a20485454502f312e310d0a486f73743a203233392e3235352e3235352e3235303a313939300d0a5365727665723a204275696c64726f6f742f323031382e30322d7263332055506e502f312e302073736470642f312e380d0a4c6f636174696f6e3a203139322
    [Length: 428]

The advertised data is just about where the printer is (local IP), and basic things. Here's the full payload:

NOTIFY * HTTP/1.1
Host: 239.255.255.250:1990
Server: Buildroot/2018.02-rc3 UPnP/1.0 ssdpd/1.8
Location: 192.168.137.49
NT: urn:bambulab-com:device:3dprinter:1
NTS: ssdp:alive
USN: 00M09A321800877
Cache-Control: max-age=1800
DevModel.bambu.com: BL-P001
DevName.bambu.com: 3DP-00M-877
DevSignal.bambu.com: -63
DevConnect.bambu.com: lan
DevBind.bambu.com: free
Devseclink.bambu.com: secure
DevInf.bambu.com: wlan0

Beyond the time sync and local advertising, the printer doesn't do much on boot.

Printing

Starting a print manually on the printer does not result in any additional data, beyond what's described above, being sent anywhere automatically. All the communication is done on a local network.

Starting a print via BambuStudio in LAN Only mode results in the BambuStudio communicating directly with the printer, no data is ever sent to the printer from any other IP, nor is the printer at any point contacting any IP except the one with BambuStudio running. The print file when sent while the printer is in LAN Only mode is 100% transferred over the local network only. Both the printer and the computer had internet access, but they never made any use of that.

Sending the file via BambuStudio as opposed to starting it from the SD card on the printer, of course, does result in more network activity, but as I said, that's all local and looks like this:

Frame 1373: 1514 bytes on wire (12112 bits), 1514 bytes captured (12112 bits) on interface \Device\NPF_{A143A799-204A-4470-8CA9-DC91CA7DBE5E}, id 0
Ethernet II, Src: aa:6d:aa:9e:f5:34 (aa:6d:aa:9e:f5:34), Dst: AMPAKTechnol_41:5d:1e (b8:13:32:41:5d:1e)
Internet Protocol Version 4, Src: 192.168.137.1, Dst: 192.168.137.164
Transmission Control Protocol, Src Port: 50275, Dst Port: 50001, Seq: 38529, Ack: 146, Len: 1460
    Source Port: 50275
    Destination Port: 50001
    [Stream index: 4]
    [Conversation completeness: Complete, WITH_DATA (63)]
    [TCP Segment Len: 1460]
    Sequence Number: 38529    (relative sequence number)
    Sequence Number (raw): 3598915728
    [Next Sequence Number: 39989    (relative sequence number)]
    Acknowledgment Number: 146    (relative ack number)
    Acknowledgment number (raw): 3233200675
    0101 .... = Header Length: 20 bytes (5)
    Flags: 0x010 (ACK)
    Window: 256
    [Calculated window size: 65536]
    [Window size scaling factor: 256]
    Checksum: 0xae59 [unverified]
    [Checksum Status: Unverified]
    Urgent Pointer: 0
    [Timestamps]
    [SEQ/ACK analysis]
    TCP payload (1460 bytes)
    [Reassembled PDU in frame: 1380]
    TCP segment data (1460 bytes)

Stopping a print and "Submitting a failure"

One of the features of X1C is that you can "Submit" the information that the print has failed, to better train the AI spaghetti detection, this option is available in LAN Only mode as well. And looks like this:

Printing Stopped screen on X1C with "Spaghetti failure" marked

However, when I marked it as "Spaghetti failure" and clicked "Submit and Close", there was no additional network activity. This information was not sent anywhere over the network. Either it just gets stored locally or dismissed, either way, the only thing that I could see when it comes to network activity were the NTP requests and local broadcasts that the printer is on the network.

Conclusion

The LAN mode is pretty quiet, and there's nothing concerning in the network data. If I had to guess, I would say that the NTP time sync is required/beneficial since things like BambuStudio can connect to the printer even in LAN Only mode (of course, only if the BambuStudio is on the same LAN as well) but it would still be nice for it to be completely silent if possible. From the user perspective, there's nothing concerning here, though.

Note: DHCP Option 42, which tells clients which NTP server to use, is not being used by X1C. This is not too surprising, as many devices ignore this, but if NTP is required, then it would be nice if it respected this option.

Online Mode

This mode is where there is communication to remote servers which is encrypted (as it should be), therefore some of the conclusions are derived based on the overall information about the request as opposed to knowing the payload exactly.

Boot + idle

Just like in the LAN mode, the printer queries the DNS to get IPs of the ntp server and advertises its presence on the local network. The printer also asks for the IP of api.bambulab.com domain. This domain is behind Cloudflare, so the IP returned is Cloudflare IP.

The printer completes the certificate handshake, and after that, we cannot see the contents of the API communication:

The following domains are also queried: e.bambulab.com, public-cdn.bblmw.com and smaller quantities of data are exchanged back and forth between different domain, primarily the API one, these tend to be a bunch of smaller payloads, here's an example of one:

Frame 276: 1454 bytes on wire (11632 bits), 1454 bytes captured (11632 bits) on interface \Device\NPF_{A143A799-204A-4470-8CA9-DC91CA7DBE5E}, id 0
Ethernet II, Src: AMPAKTechnol_41:5d:1e (b8:13:32:41:5d:1e), Dst: aa:6d:aa:9e:f5:34 (aa:6d:aa:9e:f5:34)
Internet Protocol Version 4, Src: 192.168.137.49, Dst: 104.18.2.216
Transmission Control Protocol, Src Port: 56538, Dst Port: 443, Seq: 644, Ack: 4879, Len: 1388
    Source Port: 56538
    Destination Port: 443
    [Stream index: 4]
    [Conversation completeness: Incomplete, DATA (15)]
    [TCP Segment Len: 1388]
    Sequence Number: 644    (relative sequence number)
    Sequence Number (raw): 1787096955
    [Next Sequence Number: 2032    (relative sequence number)]
    Acknowledgment Number: 4879    (relative ack number)
    Acknowledgment number (raw): 2959930512
    1000 .... = Header Length: 32 bytes (8)
    Flags: 0x010 (ACK)
    Window: 166
    [Calculated window size: 42496]
    [Window size scaling factor: 256]
    Checksum: 0xe981 [unverified]
    [Checksum Status: Unverified]
    Urgent Pointer: 0
    Options: (12 bytes), No-Operation (NOP), No-Operation (NOP), Timestamps
    [Timestamps]
    [SEQ/ACK analysis]
    TCP payload (1388 bytes)
    [Reassembled PDU in frame: 277]
    TCP segment data (1388 bytes)

Once that part is done, X1C queries for the IP of us.mqtt.bambulab.com - this is a bit surprising to me because I am in Europe, and expected the EU domain to be used for this. Nevertheless, after a few more packets, we see that BambuLab is using emqx.io for their MQTT setup. MQTT is the core of how BambuLab's 3D printers communicate with the remote servers and devices. All the servers and IPs are either behind Cloudflare or are Amazon AWS instances in the US.

Frame 636: 336 bytes on wire (2688 bits), 336 bytes captured (2688 bits) on interface \Device\NPF_{A143A799-204A-4470-8CA9-DC91CA7DBE5E}, id 0
Ethernet II, Src: aa:6d:aa:9e:f5:34 (aa:6d:aa:9e:f5:34), Dst: AMPAKTechnol_41:5d:1e (b8:13:32:41:5d:1e)
Internet Protocol Version 4, Src: 192.168.137.1, Dst: 192.168.137.49
User Datagram Protocol, Src Port: 53, Dst Port: 38328
Domain Name System (response)
    Transaction ID: 0x58a1
    Flags: 0x8100 Standard query response, No error
    Questions: 1
    Answer RRs: 4
    Authority RRs: 0
    Additional RRs: 0
    Queries
        us.mqtt.bambulab.com: type A, class IN
            Name: us.mqtt.bambulab.com
            [Name Length: 20]
            [Label Count: 4]
            Type: A (1) (Host Address)
            Class: IN (0x0001)
    Answers
        us.mqtt.bambulab.com: type CNAME, class IN, cname cc06b4cc.emqx.cloud
            Name: us.mqtt.bambulab.com
            Type: CNAME (5) (Canonical NAME for an alias)
            Class: IN (0x0001)
            Time to live: 0 (0 seconds)
            Data length: 21
            CNAME: cc06b4cc.emqx.cloud
        cc06b4cc.emqx.cloud: type CNAME, class IN, cname cc06b4cc-internet-facing-cc58a4f9438d216d.elb.us-west-2.amazonaws.com
        cc06b4cc-internet-facing-cc58a4f9438d216d.elb.us-west-2.amazonaws.com: type A, class IN, addr 34.208.11.28
        cc06b4cc-internet-facing-cc58a4f9438d216d.elb.us-west-2.amazonaws.com: type A, class IN, addr 34.215.105.148
    [Request In: 633]
    [Time: 0.033397000 seconds]

Once all of that is established, there's a small exchange of data, I'm talking about 100ish bytes to the remote servers.

Frame 471: 97 bytes on wire (776 bits), 97 bytes captured (776 bits) on interface \Device\NPF_{A143A799-204A-4470-8CA9-DC91CA7DBE5E}, id 0
Ethernet II, Src: AMPAKTechnol_41:5d:1e (b8:13:32:41:5d:1e), Dst: aa:6d:aa:9e:f5:34 (aa:6d:aa:9e:f5:34)
Internet Protocol Version 4, Src: 192.168.137.49, Dst: 34.208.11.28
    0100 .... = Version: 4
    .... 0101 = Header Length: 20 bytes (5)
    Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
    Total Length: 83
    Identification: 0x7107 (28935)
    010. .... = Flags: 0x2, Don't fragment
    ...0 0000 0000 0000 = Fragment Offset: 0
    Time to Live: 64
    Protocol: TCP (6)
    Header Checksum: 0x51d8 [validation disabled]
    [Header checksum status: Unverified]
    Source Address: 192.168.137.49
    Destination Address: 34.208.11.28
Transmission Control Protocol, Src Port: 47230, Dst Port: 8883, Seq: 94, Ack: 94, Len: 31
    Source Port: 47230
    Destination Port: 8883
    [Stream index: 0]
    [Conversation completeness: Incomplete (12)]
    [TCP Segment Len: 31]
    Sequence Number: 94    (relative sequence number)
    Sequence Number (raw): 2101739872
    [Next Sequence Number: 125    (relative sequence number)]
    Acknowledgment Number: 94    (relative ack number)
    Acknowledgment number (raw): 3750441627
    1000 .... = Header Length: 32 bytes (8)
    Flags: 0x018 (PSH, ACK)
    Window: 166
    [Calculated window size: 166]
    [Window size scaling factor: -1 (unknown)]
    Checksum: 0x14d9 [unverified]
    [Checksum Status: Unverified]
    Urgent Pointer: 0
    Options: (12 bytes), No-Operation (NOP), No-Operation (NOP), Timestamps
    [Timestamps]
    [SEQ/ACK analysis]
    TCP payload (31 bytes)
Transport Layer Security
    TLSv1.2 Record Layer: Application Data Protocol: MQ Telemetry Transport Protocol
        Content Type: Application Data (23)
        Version: TLS 1.2 (0x0303)
        Length: 26
        Encrypted Application Data: 843adea71b2659e2e75c632ec36f2e59ef800fa2892958044285
        [Application Data Protocol: MQ Telemetry Transport Protocol]

Nothing beyond this happened as long as I was observing the connection.

Using the smartphone App (Bambu Handy)

If the app is opened on the phone, then BambuLab tries to establish a peer to peer connection between the printer and the app. For this, iotcplatform.com is being used. The request for this domain happens only when the p2p connection tries to be established, or rather before it tries to be established.

The printer did not attempt to send the camera stream or establish the connection before I pressed the play video stream button in the app. I assume that other data such as temperature is sent to the app via the central MQTT server.

Frame 43: 110 bytes on wire (880 bits), 110 bytes captured (880 bits) on interface \Device\NPF_{A143A799-204A-4470-8CA9-DC91CA7DBE5E}, id 0
Ethernet II, Src: AMPAKTechnol_41:5d:1e (b8:13:32:41:5d:1e), Dst: aa:6d:aa:9e:f5:34 (aa:6d:aa:9e:f5:34)
Internet Protocol Version 4, Src: 192.168.137.49, Dst: 192.168.137.1
User Datagram Protocol, Src Port: 40842, Dst Port: 53
Domain Name System (query)
    Transaction ID: 0x3ed1
    Flags: 0x0100 Standard query
    Questions: 1
    Answer RRs: 0
    Authority RRs: 0
    Additional RRs: 0
    Queries
        all-d-master-71spctnsfa8p5xvgkssa.iotcplatform.com: type AAAA, class IN
            Name: all-d-master-71spctnsfa8p5xvgkssa.iotcplatform.com
            [Name Length: 50]
            [Label Count: 3]
            Type: AAAA (28) (IP6 Address)
            Class: IN (0x0001)
    [Response In: 45]

Once the printer establishes a peer to peer connection to the mobile app, the camera stream is sent via udp directly to the smartphone, and not to some other remote server.

Frame 252: 1159 bytes on wire (9272 bits), 1159 bytes captured (9272 bits) on interface \Device\NPF_{A143A799-204A-4470-8CA9-DC91CA7DBE5E}, id 0
Ethernet II, Src: AMPAKTechnol_41:5d:1e (b8:13:32:41:5d:1e), Dst: aa:6d:aa:9e:f5:34 (aa:6d:aa:9e:f5:34)
Internet Protocol Version 4, Src: 192.168.137.49, Dst: 93.104.153.5
User Datagram Protocol, Src Port: 35520, Dst Port: 14055
    Source Port: 35520
    Destination Port: 14055
    Length: 1125
    Checksum: 0x8e86 [unverified]
    [Checksum Status: Unverified]
    [Stream index: 11]
    [Timestamps]
    UDP payload (1117 bytes)
Data (1117 bytes)
    Data [truncated]: 4e6fad1840dc40db2d2ae83d90eece9c50e295b2800e31c5edac280c41e52a9bcd48efcf25ee65302d6b280c40e48fd8863bd71e4fb9b9dd89ddf14e0cce0bd333107bafb2aa23a786b7394bc953beaa6a6b5b90f298e8d1b6ac31c57c1330a00947247f2341ed4f398ed65b73a6a
    [Length: 1117]

Once I forced closed the app, the printer attempted to send data to it for another minute or so, but it received destination/port unreachable responses:

Frame 7369: 70 bytes on wire (560 bits), 70 bytes captured (560 bits) on interface \Device\NPF_{A143A799-204A-4470-8CA9-DC91CA7DBE5E}, id 0
Ethernet II, Src: aa:6d:aa:9e:f5:34 (aa:6d:aa:9e:f5:34), Dst: AMPAKTechnol_41:5d:1e (b8:13:32:41:5d:1e)
Internet Protocol Version 4, Src: 93.104.153.5, Dst: 192.168.137.49
Internet Control Message Protocol
    Type: 3 (Destination unreachable)
    Code: 3 (Port unreachable)
    Checksum: 0xd6fc [correct]
    [Checksum Status: Good]
    Unused: 00000000
    Internet Protocol Version 4, Src: 192.168.137.49, Dst: 93.104.153.5
    User Datagram Protocol, Src Port: 35520, Dst Port: 14055
        Source Port: 35520
        Destination Port: 14055
        Length: 89
        Checksum: 0x63ff [unverified]
        [Checksum Status: Unverified]
        [Stream index: 11]

After about a minute, the printer stopped sending the camera stream to my phone, and it reverted to just doing things it did in the idle state that I described previously.

Using the BambuStudio

Opening BambuStudio results in the same behavior as the app above, but with one difference - opening the BambuStudio is enough for the printer to start sending the camera stream to it (again, directly, not via 3rd party servers). This results in a lot of local network traffic that is not needed. My suggestion to BambuLab here would be to not start the stream immediately, even though it's not being watched.

Updating Firmware

I've updated X1C firmware from 01.07.00.00 to 01.07.01.00.

During the update the printer has received 97MB of data, and has sent 371KB worth of data. The firmware update has been downloaded from public-cdn.bambulab.com

The printer queries for this domain like this

Frame 70: 240 bytes on wire (1920 bits), 240 bytes captured (1920 bits) on interface \Device\NPF_{A143A799-204A-4470-8CA9-DC91CA7DBE5E}, id 0
Ethernet II, Src: aa:6d:aa:9e:f5:34 (aa:6d:aa:9e:f5:34), Dst: AMPAKTechnol_41:5d:1e (b8:13:32:41:5d:1e)
Internet Protocol Version 4, Src: 192.168.137.1, Dst: 192.168.137.171
User Datagram Protocol, Src Port: 53, Dst Port: 40414
    Source Port: 53
    Destination Port: 40414
    Length: 206
    Checksum: 0xf713 [unverified]
    [Checksum Status: Unverified]
    [Stream index: 4]
    [Timestamps]
    UDP payload (198 bytes)
Domain Name System (response)
    Transaction ID: 0x35ed
    Flags: 0x8100 Standard query response, No error
    Questions: 1
    Answer RRs: 5
    Authority RRs: 0
    Additional RRs: 0
    Queries
        public-cdn.bambulab.com: type A, class IN
            Name: public-cdn.bambulab.com
            [Name Length: 23]
            [Label Count: 3]
            Type: A (1) (Host Address)
            Class: IN (0x0001)
    Answers
        public-cdn.bambulab.com: type CNAME, class IN, cname d6ccirhr7xku9.cloudfront.net
        d6ccirhr7xku9.cloudfront.net: type A, class IN, addr 108.138.36.84
        d6ccirhr7xku9.cloudfront.net: type A, class IN, addr 108.138.36.22
        d6ccirhr7xku9.cloudfront.net: type A, class IN, addr 108.138.36.89
        d6ccirhr7xku9.cloudfront.net: type A, class IN, addr 108.138.36.111
    [Request In: 69]
    [Time: 0.001513000 seconds]

And then starts downloading the firmware like this:

Frame 302: 1494 bytes on wire (11952 bits), 1494 bytes captured (11952 bits) on interface \Device\NPF_{A143A799-204A-4470-8CA9-DC91CA7DBE5E}, id 0
Ethernet II, Src: aa:6d:aa:9e:f5:34 (aa:6d:aa:9e:f5:34), Dst: AMPAKTechnol_41:5d:1e (b8:13:32:41:5d:1e)
Internet Protocol Version 4, Src: 108.138.36.84, Dst: 192.168.137.171
Transmission Control Protocol, Src Port: 443, Dst Port: 50976, Seq: 106956, Ack: 843, Len: 1428
    Source Port: 443
    Destination Port: 50976
    [Stream index: 5]
    [Conversation completeness: Complete, WITH_DATA (31)]
    [TCP Segment Len: 1428]
    Sequence Number: 106956    (relative sequence number)
    Sequence Number (raw): 1085568472
    [Next Sequence Number: 108384    (relative sequence number)]
    Acknowledgment Number: 843    (relative ack number)
    Acknowledgment number (raw): 4036971652
    1000 .... = Header Length: 32 bytes (8)
    Flags: 0x018 (PSH, ACK)
    Window: 133
    [Calculated window size: 68096]
    [Window size scaling factor: 512]
    Checksum: 0x5225 [unverified]
    [Checksum Status: Unverified]
    Urgent Pointer: 0
    Options: (12 bytes), No-Operation (NOP), No-Operation (NOP), Timestamps
    [Timestamps]
    [SEQ/ACK analysis]
    TCP payload (1428 bytes)
    [Reassembled PDU in frame: 313]
    TCP segment data (1428 bytes)

The only thing that the printer sends during this process is acknowledgments on the data received, since data is transferred in chunks, this results in many acknowledgments - thus resulting in the scent data size described above.

The acknowledgment packet looks like this

Frame 315: 66 bytes on wire (528 bits), 66 bytes captured (528 bits) on interface \Device\NPF_{A143A799-204A-4470-8CA9-DC91CA7DBE5E}, id 0
Ethernet II, Src: AMPAKTechnol_41:5d:1e (b8:13:32:41:5d:1e), Dst: aa:6d:aa:9e:f5:34 (aa:6d:aa:9e:f5:34)
Internet Protocol Version 4, Src: 192.168.137.171, Dst: 108.138.36.84
Transmission Control Protocol, Src Port: 50976, Dst Port: 443, Seq: 843, Ack: 72684, Len: 0
    Source Port: 50976
    Destination Port: 443
    [Stream index: 5]
    [Conversation completeness: Complete, WITH_DATA (31)]
    [TCP Segment Len: 0]
    Sequence Number: 843    (relative sequence number)
    Sequence Number (raw): 4036971652
    [Next Sequence Number: 843    (relative sequence number)]
    Acknowledgment Number: 72684    (relative ack number)
    Acknowledgment number (raw): 1085534200
    1000 .... = Header Length: 32 bytes (8)
    Flags: 0x010 (ACK)
    Window: 391
    [Calculated window size: 100096]
    [Window size scaling factor: 256]
    Checksum: 0x6328 [unverified]
    [Checksum Status: Unverified]
    Urgent Pointer: 0
    Options: (12 bytes), No-Operation (NOP), No-Operation (NOP), Timestamps
    [Timestamps]
    [SEQ/ACK analysis]

I've removed a bit of data, since I've had capture running before the update started so the numbers are slightly off (measured in KB) but are limited to firmware update alone:

Address Packets Bytes Total Packets Percent Filtered Tx Packets Tx Bytes Rx Packets Rx Bytes
108.138.36.84 70453 97089145 70453 100 64815 96713477 5638 375668
192.168.137.171 71655 97689176 71655 100 6210 514133 65445 97175043

Once the firmware update is done, the printer reboots and exhibits the same behavior as described above.

Starting and stopping the print via cloud

No additional domains are used. The data is sent to Amazon AWS in US West-1, and the data is sent to the printer via tcp.

The DNS query for the server looks like this:

Frame 829: 240 bytes on wire (1920 bits), 240 bytes captured (1920 bits) on interface \Device\NPF_{A143A799-204A-4470-8CA9-DC91CA7DBE5E}, id 0
Ethernet II, Src: aa:6d:aa:9e:f5:34 (aa:6d:aa:9e:f5:34), Dst: AMPAKTechnol_41:5d:1e (b8:13:32:41:5d:1e)
Internet Protocol Version 4, Src: 192.168.137.1, Dst: 192.168.137.49
User Datagram Protocol, Src Port: 53, Dst Port: 50405
    Source Port: 53
    Destination Port: 50405
    Length: 206
    Checksum: 0xa9f6 [unverified]
    [Checksum Status: Unverified]
    [Stream index: 9]
    [Timestamps]
    UDP payload (198 bytes)
Domain Name System (response)
    Transaction ID: 0x2af6
    Flags: 0x8100 Standard query response, No error
    Questions: 1
    Answer RRs: 8
    Authority RRs: 0
    Additional RRs: 0
    Queries
        s3.us-west-1.amazonaws.com: type A, class IN
            Name: s3.us-west-1.amazonaws.com
            [Name Length: 26]
            [Label Count: 4]
            Type: A (1) (Host Address)
            Class: IN (0x0001)
    Answers
        s3.us-west-1.amazonaws.com: type A, class IN, addr 52.219.193.0
        s3.us-west-1.amazonaws.com: type A, class IN, addr 52.219.221.0
        s3.us-west-1.amazonaws.com: type A, class IN, addr 52.219.193.184
        s3.us-west-1.amazonaws.com: type A, class IN, addr 52.219.194.104
        s3.us-west-1.amazonaws.com: type A, class IN, addr 52.219.113.168
        s3.us-west-1.amazonaws.com: type A, class IN, addr 52.219.120.40
        s3.us-west-1.amazonaws.com: type A, class IN, addr 52.219.117.96
        s3.us-west-1.amazonaws.com: type A, class IN, addr 52.219.113.128
    [Request In: 827]
    [Time: 0.016847000 seconds]

The data itself is encrypted (SSL).

Stopping the print does not result in anything else that's different than idle mode, except that the data is sent immediately to the remote servers so that the state gets updated, I would assume.

Conclusion

BambuLab X1C in online mode does not send any large quantities of data, camera stream is sent p2p whenever possible so it doesn't even pass through other servers. However, on the client side of things, specifically BambuStudio, this could be optimized - as it is a bit noisy now. The data sent to the server is encrypted, as it should be btw, so take that any way you want. I have not seen anything that would indicate that prints started locally are somehow sent to remote servers, and those started via cloud are sent to Amazon's AWS servers, seemingly in the US regardless of the printer region, which in my case is set to be EU. This is something BambuLab should also look into.

BambuLab also seems to use off the shelf solutions for businesses for their MQTT, CDN, P2P connection establishing, and a few other things instead of re-inventing the wheel, which I, personally, see as a positive thing.

Bonus: Local MQTT Server

Did you know your X1C has a local MQTT server running? You can access it by connecting with the user bblp on port 8883 without SSL and TLS verification, and the password is the LAN Only Access Code you can view on your printer's screen.

Based on the message sizes I would guess that the data sent to the remote servers looks very similar to the one in local MQTT server which looks like this:

{
    "print": {
        "ams": {
            "ams": [
                {
                    "humidity": "3",
                    "id": "0",
                    "temp": "23.7",
                    "tray": [
                        {
                            "bed_temp": "0",
                            "bed_temp_type": "0",
                            "cali_idx": -1,
                            "cols": [
                                "898989FF"
                            ],
                            "ctype": 0,
                            "drying_temp": "0",
                            "drying_time": "0",
                            "id": "0",
                            "nozzle_temp_max": "240",
                            "nozzle_temp_min": "190",
                            "remain": -1,
                            "tag_uid": "0000000000000000",
                            "tray_color": "898989FF",
                            "tray_diameter": "0.00",
                            "tray_id_name": "",
                            "tray_info_idx": "GFL99",
                            "tray_sub_brands": "",
                            "tray_type": "PLA",
                            "tray_uuid": "00000000000000000000000000000000",
                            "tray_weight": "0",
                            "xcam_info": "000000000000000000000000"
                        },
                        {
                            "bed_temp": "0",
                            "bed_temp_type": "0",
                            "cali_idx": -1,
                            "cols": [
                                "161616FF"
                            ],
                            "ctype": 0,
                            "drying_temp": "0",
                            "drying_time": "0",
                            "id": "1",
                            "nozzle_temp_max": "240",
                            "nozzle_temp_min": "190",
                            "remain": -1,
                            "tag_uid": "0000000000000000",
                            "tray_color": "161616FF",
                            "tray_diameter": "0.00",
                            "tray_id_name": "",
                            "tray_info_idx": "GFL99",
                            "tray_sub_brands": "",
                            "tray_type": "PLA",
                            "tray_uuid": "00000000000000000000000000000000",
                            "tray_weight": "0",
                            "xcam_info": "000000000000000000000000"
                        },
                        {
                            "bed_temp": "0",
                            "bed_temp_type": "0",
                            "cali_idx": -1,
                            "cols": [
                                "F72323FF"
                            ],
                            "ctype": 0,
                            "drying_temp": "0",
                            "drying_time": "0",
                            "id": "2",
                            "nozzle_temp_max": "240",
                            "nozzle_temp_min": "190",
                            "remain": -1,
                            "tag_uid": "0000000000000000",
                            "tray_color": "F72323FF",
                            "tray_diameter": "0.00",
                            "tray_id_name": "",
                            "tray_info_idx": "GFA00",
                            "tray_sub_brands": "",
                            "tray_type": "PLA",
                            "tray_uuid": "00000000000000000000000000000000",
                            "tray_weight": "0",
                            "xcam_info": "000000000000000000000000"
                        },
                        {
                            "bed_temp": "0",
                            "bed_temp_type": "0",
                            "cali_idx": -1,
                            "cols": [
                                "FFFFFFFF"
                            ],
                            "ctype": 0,
                            "drying_temp": "0",
                            "drying_time": "0",
                            "id": "3",
                            "nozzle_temp_max": "270",
                            "nozzle_temp_min": "220",
                            "remain": -1,
                            "tag_uid": "0000000000000000",
                            "tray_color": "FFFFFFFF",
                            "tray_diameter": "0.00",
                            "tray_id_name": "",
                            "tray_info_idx": "GFG99",
                            "tray_sub_brands": "",
                            "tray_type": "PETG",
                            "tray_uuid": "00000000000000000000000000000000",
                            "tray_weight": "0",
                            "xcam_info": "000000000000000000000000"
                        }
                    ]
                }
            ],
            "ams_exist_bits": "1",
            "insert_flag": true,
            "power_on_flag": false,
            "tray_exist_bits": "f",
            "tray_is_bbl_bits": "f",
            "tray_now": "2",
            "tray_pre": "2",
            "tray_read_done_bits": "f",
            "tray_reading_bits": "0",
            "tray_tar": "255",
            "version": 185
        },
        "ams_rfid_status": 6,
        "ams_status": 0,
        "aux_part_fan": true,
        "bed_target_temper": 0.0,
        "bed_temper": 20.0,
        "big_fan1_speed": "0",
        "big_fan2_speed": "0",
        "cali_version": 0,
        "chamber_temper": 25.0,
        "command": "push_status",
        "cooling_fan_speed": "0",
        "ctt": 0,
        "fail_reason": "0",
        "fan_gear": 0,
        "filam_bak": [],
        "force_upgrade": false,
        "gcode_file": "",
        "gcode_file_prepare_percent": "0",
        "gcode_start_time": "0",
        "gcode_state": "IDLE",
        "heatbreak_fan_speed": "0",
        "hms": [],
        "home_flag": 6409608,
        "hw_switch_state": 1,
        "ipcam": {
            "ipcam_dev": "1",
            "ipcam_record": "enable",
            "mode_bits": 2,
            "resolution": "1080p",
            "rtsp_url": "disable",
            "timelapse": "disable",
            "tutk_server": "enable"
        },
        "job_id": "",
        "layer_num": 0,
        "lifecycle": "product",
        "lights_report": [
            {
                "mode": "on",
                "node": "chamber_light"
            },
            {
                "mode": "flashing",
                "node": "work_light"
            }
        ],
        "maintain": 3,
        "mc_percent": 0,
        "mc_print_error_code": "0",
        "mc_print_stage": "1",
        "mc_print_sub_stage": 0,
        "mc_remaining_time": 0,
        "mess_production_state": "active",
        "net": {
            "conf": 16,
            "info": [
                {
                    "ip": 688521226,
                    "mask": 15794175
                },
                {
                    "ip": 0,
                    "mask": 0
                }
            ]
        },
        "nozzle_diameter": "0.4",
        "nozzle_target_temper": 0.0,
        "nozzle_temper": 21.0,
        "nozzle_type": "hardened_steel",
        "online": {
            "ahb": false,
            "ext": false,
            "version": 7
        },
        "print_error": 0,
        "print_gcode_action": 0,
        "print_real_action": 0,
        "print_type": "",
        "profile_id": "",
        "project_id": "",
        "queue_est": 0,
        "queue_number": 0,
        "queue_sts": 0,
        "queue_total": 0,
        "s_obj": [],
        "sdcard": true,
        "sequence_id": "2021",
        "spd_lvl": 2,
        "spd_mag": 100,
        "stg": [],
        "stg_cur": -1,
        "subtask_id": "",
        "subtask_name": "",
        "task_id": "",
        "total_layer_num": 0,
        "upgrade_state": {
            "ahb_new_version_number": "",
            "ams_new_version_number": "",
            "consistency_request": false,
            "dis_state": 1,
            "err_code": 0,
            "ext_new_version_number": "",
            "force_upgrade": false,
            "idx": 7,
            "message": "",
            "module": "null",
            "new_version_state": 1,
            "ota_new_version_number": "01.07.01.00",
            "progress": "0",
            "sequence_id": 0,
            "sn": "<redacted>",
            "status": "IDLE"
        },
        "upload": {
            "file_size": 0,
            "finish_size": 0,
            "message": "Good",
            "oss_url": "",
            "progress": 0,
            "sequence_id": "0903",
            "speed": 0,
            "status": "idle",
            "task_id": "",
            "time_remaining": 0,
            "trouble_id": ""
        },
        "vt_tray": {
            "bed_temp": "0",
            "bed_temp_type": "0",
            "cali_idx": -1,
            "cols": [
                "00000000"
            ],
            "ctype": 0,
            "drying_temp": "0",
            "drying_time": "0",
            "id": "254",
            "nozzle_temp_max": "0",
            "nozzle_temp_min": "0",
            "remain": 0,
            "tag_uid": "0000000000000000",
            "tray_color": "00000000",
            "tray_diameter": "0.00",
            "tray_id_name": "",
            "tray_info_idx": "",
            "tray_sub_brands": "",
            "tray_type": "",
            "tray_uuid": "00000000000000000000000000000000",
            "tray_weight": "0",
            "xcam_info": "000000000000000000000000"
        },
        "wifi_signal": "-44dBm",
        "xcam": {
            "allow_skip_parts": false,
            "buildplate_marker_detector": true,
            "first_layer_inspector": true,
            "halt_print_sensitivity": "medium",
            "print_halt": true,
            "printing_monitor": true,
            "spaghetti_detector": true
        },
        "xcam_status": "0"
    }
}

Final Conclusion

The printer offers convenience features over the internet, these seem to be implemented with privacy where possible - for example, camera stream. But on the other hand, using the cloud to send files results, well... in files being sent to the cloud. This may not be desired by everyone. The data is sent to Amazon's AWS instances physically located in the US West.

LAN only mode seems to offer the most of the convenience features if you're in the same network, and based on my research, seems to be properly implemented without leaking information or data to BambuLab. You can also additionally isolate the printer via a router or a firewall.

Offline mode seems to be the safest in every way, as the printer never attempts to make any sort of connection to either known or open networks.

I think that all 3 modes are reasonable and come with reasonable benefits and drawbacks, and the appropriate one should be picked for whatever level of security and privacy you need.