DEV Community

loading...
Cover image for S'tracing WO MIC: a journey involving threading, networking, audio, and more

S'tracing WO MIC: a journey involving threading, networking, audio, and more

cppchedy profile image cppchedy ・17 min read

Recently, I decided to solve the issue of my broken camera without buying a new one. The idea is to develop a system capable of forwarding my phone's camera stream to my laptop and expose it to other applications.

Using a phone to extend the capability of your machine isn't new. In fact, the Playstore is rich with such apps. For example, WO MIC -an android app- enables computers to use the mic of a mobile phone by forwarding the audio input via Wi-Fi, Wi-Fi Direct, USB, or Bluetooth.

Wo MIC serves as a good case of study for my project. Understanding how it works underneath and seeing how exactly it uses the mentioned technologies will certainly provide a lot of insight when I am designing my app. Also, checking my assumptions about the system implementation will solidify my design skills.

In this article, I cover the architecture of the WO MIC system. Moreover, I go through the process of analyzing WO MIC client using strace: I document its pitfalls and explain a couple of concepts like threads from the Linux kernel's point of view.

Architecture of WO MIC

Architecture of WO MIC

The above image shows the architecture of the WO MIC system for sharing a phone's mic with a laptop. Besides the Android app, the laptop contains a WO MIC client program responsible for receiving the streams through the router and writing it to the snd-aloop module.

Snd-aloop is a Linux kernel module that brings a loopback feature for audio I/O. When this module is loaded into the kernel, it adds a virtual device called Loopback card in which other application can read from or write to.

WO MIC's How to

After installing the android app from the Playstore, make sure that you are connected to your Wi-fi access point then open WO MIC. Select Wi-fi in the settings and click on start. An IP address is assigned and shown on the screen. This IP address will be used by the WO MIC client for connection.

Now on your laptop, Download the client. Make sure the snd-aloop module is loaded by typing the following command in your terminal:

$ sudo modprobe snd-aloop
Enter fullscreen mode Exit fullscreen mode

To check if the cmd was successful, you can use the lsmod command and look for a module named snd-aloop in the output. if everything is okay with snd-aloop, we can now connect to the phone by:

$ ./micclient-x86_64.AppImage -t Wifi IP_addr_displayed_on_phone
Enter fullscreen mode Exit fullscreen mode

you will see the message connected on the phone when the two machines are linked.

Stracing the WO MIC client

In the Architecture section, I presented my thoughts on how the WO MIC system works. In this section, I will be verifying what we discussed using the tool strace.

strace is a powerful command-line tool for debugging and troubleshooting programs in Linux. In my case, I will be using it to capture all the system calls executed by the WO MIC client. Therefore, with this tool's help, we will be able to understand the mechanics of the system.

Based on our own assumption, The network stack and the audio APIs are the principal components used by the WO MIC client. Accordingly, for the networking part, we will be searching for network system calls such as connect, socket, recvmsg, sendmsg, etc...

In contrast to networking, I am not sure about the audio part: I never encountered a situation where I had to look deep into the audio system calls in Linux. This leaves us with no choice but to explore all the output of strace in order to find the sound APIs. Nevertheless, This is a good opportunity for us to learn them. Further more, we could discover other interesting API while we are exploring.

Note: if you want to follow along refer to the How to section of this article or go to this page so that you know how to run WO MIC.

Let's start tracing. Connect your phone to your Wi-fi access point and start the service then switch to your laptop and type the following in your terminal:

$ strace ./micclient-x86_64.AppImage -t Wifi 192.168.102
Enter fullscreen mode Exit fullscreen mode

Note: replace the IP address with the one shown on your phone's screen.

strace launched on the client

The tracing starts with calls to load the required dynamic libraries with the right protection for their memory regions.

Audio APIs

The first thing I did was to look for network system calls. Unexpectedly, I didn't encounter any. Instead, I stumbled upon some S.C that give the feeling of executing audio work.

//...
openat(AT_FDCWD, "/dev/snd/controlC0", O_RDWR|O_CLOEXEC) = 4
ioctl(4, SNDRV_CTL_IOCTL_PVERSION, 0x7ffc8994f804) = 0
ioctl(4, SNDRV_CTL_IOCTL_CARD_INFO, 0x7ffc8994f890) = 0
close(4)                                = 0
openat(AT_FDCWD, "/dev/snd/controlC1", O_RDWR|O_CLOEXEC) = 4
ioctl(4, SNDRV_CTL_IOCTL_PVERSION, 0x7ffc8994f804) = 0
ioctl(4, SNDRV_CTL_IOCTL_CARD_INFO, 0x7ffc8994f890) = 0
close(4)                                = 0
openat(AT_FDCWD, "/dev/snd/controlC1", O_RDWR|O_CLOEXEC) = 4
ioctl(4, SNDRV_CTL_IOCTL_PVERSION, 0x7ffc8994f814) = 0
ioctl(4, SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE, 0x7ffc8994f87c) = 0
openat(AT_FDCWD, "/dev/snd/pcmC1D0p", O_RDWR|O_NONBLOCK|O_CLOEXEC) = 5
ioctl(5, SNDRV_PCM_IOCTL_INFO, 0x7ffc8994f8b0) = 0
close(4)                                = 0
ioctl(5, SNDRV_PCM_IOCTL_INFO, 0x7ffc8994f720) = 0
fcntl(5, F_GETFL)                       = 0x8802 (flags O_RDWR|O_NONBLOCK|O_LARGEFILE)
ioctl(5, SNDRV_PCM_IOCTL_PVERSION, 0x7ffc8994f700) = 0
ioctl(5, SNDRV_PCM_IOCTL_USER_PVERSION, 0x7ffc8994f710) = 0
ioctl(5, SNDRV_PCM_IOCTL_TTSTAMP, 0x7ffc8994f704) = 0
mmap(NULL, 4096, PROT_READ, MAP_SHARED, 5, 0x80000000) = 0x7efc7410b000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, 5, 0x81000000) = 0x7efc740e0000
fcntl(5, F_GETFL)                       = 0x8802 (flags O_RDWR|O_NONBLOCK|O_LARGEFILE)
fcntl(5, F_SETFL, O_RDWR|O_LARGEFILE)   = 0
ioctl(5, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ffc8994f900) = 0
ioctl(5, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ffc8994f500) = 0
ioctl(5, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ffc8994f500) = 0
ioctl(5, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ffc8994f230) = 0
ioctl(5, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ffc8994f500) = 0
ioctl(5, SNDRV_PCM_IOCTL_HW_REFINE, 0x7ffc8994f500) = 0
//...
Enter fullscreen mode Exit fullscreen mode

After a bit of googling about the requests (SNDRV_PCM_IOCTL_xx_xx) passed to ioctl, I found about lib-alsa which is an API for doing PCM playback and recording in Linux. So I was indeed correct with my guessing. We now know what the client used for manipulating audio.

Bonus: for a complete guide on which Sound API to use read this post.

Network APIs

on the contrary of what I was expecting, finding the network APIs wasn't obvious. From the output, there are clearly log messages describing the state of the connection but no API calls were visible. the following image shows the logging of connection states:
Logs of connection states

I thought that this has to do with threading. In other words, My guess is that the networking part is being handled by a different thread.

Before checking if my guess is correct, let's make a little detour and talk about threads in Linux.

Detour on threads in Linux

Certainly, anyone who took a course related to operating systems will be exposed to the concept of threads defined as lightweight processes. This is true for UNIX-like operating systems except for the Linux kernel.

In Linux, a thread is a process. Why, you ask? because the structure holding processes states(formally know as Process Control Block or PCB) is already very lightweight in comparison with other operating systems like Windows. So the design direction wasn't in favor of defining a new representation for threads. Instead, Threads are managed in groups of processes.

The Linux kernel associates processes that are created by the same parent process with each other by leveraging a couple of fields in the PCB. To repeat my point, there is no new structure for handling threads.

The name of the C structure that holds information about the processes is task_struct. You can check it out in the Linux kernel codebase, specifically the sched.h header file under include/linux.

Using This parent-child relationship to explain threads may be confusing(if you forget sharing resources) because all the processes are created from parents- until we arrive at the root, init.d- and each user process has a parent, so concretely, How does the Linux kernel see threads?

From the Linux kernel perspective a thread could be defined as a process that shares with its parent:

  • the address space,
  • filesystem resources,
  • file descriptors,
  • and signal handlers

Given the previous definition, which system call is responsible for making threads?

Let's start with the famous API fork. does this S.C spawn a new thread? The following is a quote of the description of fork from man:

fork() creates a new process by duplicating the calling process.
The new process is referred to as the child process. The calling
process is referred to as the parent process.

Clearly, this has the parent-child relationship but:

The child process and the parent process run in separate memory
spaces. At the time of fork() both memory spaces have the same
content. Memory writes, file mappings (mmap(2)), and unmappings
(munmap(2)) performed by one of the processes do not affect the
other.

As we can see, fork does make a process duplicate but it shares almost nothing. In fact, a call to exec will populate the memory space with the new instructions from the specified binary(UNIX use this approach to spawn a new process).

Further down in fork's description from man, we encounter this interesting paragraph about the difference between C library and the Linux kernel:

C library/kernel differences
Since version 2.3.3, rather than invoking the kernel's fork()
system call, the glibc fork() wrapper that is provided as part of
the NPTL threading implementation invokes clone(2) with flags
that provide the same effect as the traditional system call. (A
call to fork() is equivalent to a call to clone(2) specifying
flags as just SIGCHLD.)

This quote is saying that glibc's fork wrapper is now implemented using the clone system call rather then directly calling the fork system call of the kernel. To be exact, the call to clone is like this: clone(SIGCHLD, 0).

The system call clone is similar to fork in the sense that it spawns a new process. However, there is a crucial difference:

By contrast with fork(2), these system calls provide more precise
control over what pieces of execution context are shared between
the calling process and the child process.

In other words, clone gives you explicit control over what to share with your child process. This is exactly what we need.

clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
Enter fullscreen mode Exit fullscreen mode

The above call specifies the sharing of the Virtual memory with CLONE_VM, the sharing of files systems CLONE_FS, the sharing of the files CLONE_FILES, and the sharing of the signal handler CLONE_SIGHAND. This is even with the previous definition of threads in Linux.

Note: POSIX thread(or pthread)'s implementation of pthread_create involves more flags: CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM | CLONE_SIGHAND | CLONE_THREAD | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID.

Before proceeding to the next subsection, keep in mind the definition of Linux threads.

Looking for network system calls in forks

Why did we have the discussion about threads? it's because we are suspecting that the work of the network exchanges is handled in other threads. For this reason, we need to know how exactly threads are created in order to recognize the calls in the trace.

Looking into strace output, there are two calls to clone. In the first call we have:

Clone first call

These flags, CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, are passed to clone. Therefore, we can say that this call is for a new process and not a new thread. if you recall the glibc's fork is implemented by passing SIGCHLD to clone. This flag is present in this call.

In the second call, we have:
Clone second call

According to what we discussed about threads, this call contains the same flags as the POSIX pthread_create: CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID. So this is a confirmation that WO MIC client is using threads. Moreover, I found other evidence that indicates the usage of threading such as loading the dynamic library(.so) of pthread.

Let's go back to strace now. For following forks of a process, strace provide the option -f. Executing strace with this option yields the following result:

strace with f option

Even though we can trace the forks of our parent process, now, the client exits with a nonzero return value. Moreover, the connection with the phone isn't established. You can compare the two output with and without -f in the following image:

different output for strace when adding the f option

In the new trace output, I spotted a couple of calls to connect and socket but they are not meant for connecting to the phone. Instead, they are doing inter-process communication(IPC) with UNIX Domain sockets:

//...
[pid 97612] socket(AF_UNIX, SOCK_STREAM, 0) = 9
[pid 97612] fcntl(9, F_GETFL)           = 0x2 (flags O_RDWR)
[pid 97612] fcntl(9, F_SETFL, O_RDWR|O_NONBLOCK) = 0
[pid 97612] fcntl(9, F_GETFD)           = 0
[pid 97612] fcntl(9, F_SETFD, FD_CLOEXEC) = 0
[pid 97612] connect(9, {sa_family=AF_UNIX, sun_path="/var/lib/sss/pipes/nss"}, 110) = 0
[pid 97612] fstat(9, {st_mode=S_IFSOCK|0777, st_size=0, ...}) = 0
[pid 97612] poll([{fd=9, events=POLLOUT}], 1, 300000) = 1 ([{fd=9, revents=POLLOUT}])
[pid 97612] sendto(9, "\24\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0", 16, MSG_NOSIGNAL, NULL, 0) = 16
[pid 97612] poll([{fd=9, events=POLLOUT}], 1, 300000) = 1 ([{fd=9, revents=POLLOUT}])
[pid 97612] sendto(9, "\1\0\0\0", 4, MSG_NOSIGNAL, NULL, 0) = 4
[pid 97612] poll([{fd=9, events=POLLIN}], 1, 300000) = 1 ([{fd=9, revents=POLLIN}])
[pid 97612] read(9, "\24\0\0\0\1\0\0\0\0\0\0\0\0\0\0\0", 16) = 16
//...
Enter fullscreen mode Exit fullscreen mode

Note that the second call to clone isn't executed in this instance.

Going through the traces, I understood that the process is failing to mount /dev/fuse which is vital to its inner working.

//...
[pid 97613] getgid()                    = 1000
[pid 97613] setfsgid(1000)              = 1000
[pid 97613] openat(AT_FDCWD, "/dev/fuse", O_RDWR) = 6
[pid 97613] getuid()                    = 1000
//...
[pid 97613] mount("/dev/fuse", ".", "fuse", MS_RDONLY|MS_NOSUID|MS_NODEV, "fd=6,rootmode=40000,user_id=1000"...) = -1 EPERM (Operation not permitted)
[pid 97613] write(2, "fusermount: mount failed: Operat"..., 50fusermount: mount failed: Operation not permitted
) = 50
[pid 97613] close(6)                    = 0
[pid 97613] exit_group(1)               = ?
Enter fullscreen mode Exit fullscreen mode

As indicated in the message, the process don't have permission to mount fusermount.

To fix this problem, I added sudo to the cmd, so now we have:

$ sudo strace -f ./micclient-x86_64.AppImage -t Wifi 192.168.1.102
Enter fullscreen mode Exit fullscreen mode

Finally, after fixing the permission problem, the network system calls are printed for pid 98629:

network system calls

The above image shows a messy output. It is better to filter out any unneeded system calls and keep only the network ones.
strace provides an option for this:

$ sudo strace -tt -e trace=network -f ./micclient-x86_64.AppImage -t Wifi 192.168.1.102
Enter fullscreen mode Exit fullscreen mode

We only keep the network stack calls by adding -e trace=network. The -tt option is for outputting time with each call. The result of executing this cmd is shown in the following figure:
output of filtred trace

Now that we cleared the output and localized the network system calls, it is time for us to study the content and understand what's going on.

//...
[pid 16936] 16:44:07.067208 socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 4                                                                                                
[pid 16936] 16:44:07.067421 connect(4, {sa_family=AF_INET, sin_port=htons(8125), sin_addr=inet_addr("192.168.1.102")}, 16) = 0                                           
[pid 16936] 16:44:07.240644 socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP) = 6                                                                                                 
[pid 16936] 16:44:07.241179 setsockopt(6, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0                                                                                          
[pid 16936] 16:44:07.241418 bind(6, {sa_family=AF_INET6, sin6_port=htons(49152), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::", &sin6_addr), sin6_scope_id=0}, 28) = $
[pid 16936] 16:44:07.241612 getsockopt(6, SOL_SOCKET, SO_RCVBUF, [212992], [4]) = 0 
[pid 16936] 16:44:07.241876 sendto(4, "e", 1, 0, NULL, 0) = 1                                                                                                            
[pid 16936] 16:44:07.251950 sendto(4, "\0\0\0\6", 4, 0, NULL, 0) = 4                                                                                                     
[pid 16936] 16:44:07.252200 sendto(4, "\4", 1, 0, NULL, 0) = 1                                                                                                           
[pid 16936] 16:44:07.252452 sendto(4, "\4", 1, 0, NULL, 0) = 1                                                                                                           
[pid 16936] 16:44:07.252702 sendto(4, "\6", 1, 0, NULL, 0) = 1                                                                                                           
[pid 16936] 16:44:07.252824 sendto(4, "\2", 1, 0, NULL, 0) = 1                                                                                                           
[pid 16936] 16:44:07.252940 sendto(4, "\0", 1, 0, NULL, 0) = 1                                                                                                           
[pid 16936] 16:44:07.253143 sendto(4, "\0", 1, 0, NULL, 0) = 1
[pid 16936] 16:44:07.456798 recvfrom(4, "e", 1, 0, NULL, NULL) = 1                                                                                                       
[pid 16936] 16:44:07.462912 recvfrom(4, "\0\0\0\2", 4, 0, NULL, NULL) = 4                                                                                                
[pid 16936] 16:44:07.463288 recvfrom(4, "\0", 1, 0, NULL, NULL) = 1                                                                                                      
[pid 16936] 16:44:07.463499 recvfrom(4, "\4", 1, 0, NULL, NULL) = 1                                                                                                      
[pid 16936] 16:44:07.463671 sendto(4, "f", 1, 0, NULL, 0) = 1                                                                                                            
[pid 16936] 16:44:07.463862 sendto(4, "\0\0\0\6", 4, 0, NULL, 0) = 4
//...
Connected     
strace: Process 16938 attached
strace: Process 16939 attached
[pid 16938] 16:44:07.915884 recvfrom(6, "\4\0\0\275\0\0\0\0\0\0\5\370{\346s\374\261-b\227\335\363\374\21\245\273\263\202~z\300r"..., 1024, 0, NULL, NULL) = 193
[pid 16938] 16:44:07.933227 recvfrom(6, "\4\0\0v\0\1\0\0\0\1\5\370\305<d{\"b\242\254\327t\274d\224\332\2520\02728@"..., 1024, 0, NULL, NULL) = 122
//...
Enter fullscreen mode Exit fullscreen mode

First, from looking at the calls, we find that there is actually two sockets. One socket is of the type SOCK_STRAM(that means TCP protocol) while the other socket is of the type SOCK_DGRAM (means UDP). Second, the TCP socket uses IP version 4 and the UDP socket uses IP version 6. Finally, the UDP socket starts communicating with the other side only when the TCP socket sends and receives some bytes and exactly after the message Connected is printed to stdout.

//...
[pid 16936] 16:44:07.241876 sendto(4, "e", 1, 0, NULL, 0) = 1                                                                                                            
[pid 16936] 16:44:07.251950 sendto(4, "\0\0\0\6", 4, 0, NULL, 0) = 4                                                                                                     
[pid 16936] 16:44:07.252200 sendto(4, "\4", 1, 0, NULL, 0) = 1                                                                                                           
[pid 16936] 16:44:07.252452 sendto(4, "\4", 1, 0, NULL, 0) = 1                                                                                                           
[pid 16936] 16:44:07.252702 sendto(4, "\6", 1, 0, NULL, 0) = 1                                                                                                           
[pid 16936] 16:44:07.252824 sendto(4, "\2", 1, 0, NULL, 0) = 1                                                                                                           
[pid 16936] 16:44:07.252940 sendto(4, "\0", 1, 0, NULL, 0) = 1                                                                                                           
[pid 16936] 16:44:07.253143 sendto(4, "\0", 1, 0, NULL, 0) = 1                                                                                                           
[pid 16936] 16:44:07.456798 recvfrom(4, "e", 1, 0, NULL, NULL) = 1                                                                                                       
[pid 16936] 16:44:07.462912 recvfrom(4, "\0\0\0\2", 4, 0, NULL, NULL) = 4                                                                                                
[pid 16936] 16:44:07.463288 recvfrom(4, "\0", 1, 0, NULL, NULL) = 1                                                                                                      
[pid 16936] 16:44:07.463499 recvfrom(4, "\4", 1, 0, NULL, NULL) = 1                                                                                                      
[pid 16936] 16:44:07.463671 sendto(4, "f", 1, 0, NULL, 0) = 1                                                                                                            
[pid 16936] 16:44:07.463862 sendto(4, "\0\0\0\6", 4, 0, NULL, 0) = 4                                                                                                     
[pid 16936] 16:44:07.464077 sendto(4, "\2", 1, 0, NULL, 0) = 1                                                                                                           
[pid 16936] 16:44:07.464165 sendto(4, "\2", 1, 0, NULL, 0) = 1                                                                                                           
[pid 16936] 16:44:07.464291 sendto(4, "\0\0\300\0", 4, 0, NULL, 0) = 4                                                                                                   
[pid 16936] 16:44:07.469806 recvfrom(4, "f", 1, 0, NULL, NULL) = 1                                                                                                       
[pid 16936] 16:44:07.471391 recvfrom(4, "\0\0\0\1", 4, 0, NULL, NULL) = 4                                                                                                
[pid 16936] 16:44:07.471551 recvfrom(4, "\0", 1, 0, NULL, NULL) = 1                                                                                                      
[pid 16936] 16:44:07.471675 sendto(4, "g", 1, 0, NULL, 0) = 1                                                                                                            
[pid 16936] 16:44:07.471989 sendto(4, "\0\0\0\0", 4, 0, NULL, 0) = 4                                                                                                     
[pid 16936] 16:44:07.520045 recvfrom(4, "g", 1, 0, NULL, NULL) = 1                                                                                                       
[pid 16936] 16:44:07.521841 recvfrom(4, "\0\0\0\1", 4, 0, NULL, NULL) = 4                                                                                                
[pid 16936] 16:44:07.522076 recvfrom(4, "\0", 1, 0, NULL, NULL) = 1 
//...
Enter fullscreen mode Exit fullscreen mode

After this sequence of exchange, the TCP socket enters in send-only mode.

Interestingly, I wasn't quite right about the specifics of WO MIC in the sense that UDP was out of my equation. More importantly, I didn't expect the system to establish two channels and to use the second channel(UDP) for transferring the audio samples.

On one hand, The UDP socket is bound to an IPv6 address and only recv from the other end, no sending is performed. On the other hand, the TCP socket sends small chunks (and possibly the ipv6 address at the beginning though I am not sure) of data that triggers the sending of the audio samples through the UDP channel.

In a more verbose output, we find the multiplexing I/O system call select which keeps tracks of the UDP socket descriptor changes for IN events.

20952 <... select resumed>)             = 1 (in [6], left {tv_sec=2, tv_usec=920556})
20952 recvfrom(6, "\4\0\0\212\0E\0\0\5\\\5\370P\337\362\26g\5h\305\24y\233\360=\4Tk\350\f\260\324"..., 1024, 0, NULL, NULL) = 142
20952 select(7, [6], NULL, NULL, {tv_sec=3, tv_usec=0}) = 1 (in [6], left {tv_sec=2, tv_usec=999994})
20952 recvfrom(6, "\4\0\0\211\0F\0\0\5l\5\370?\276\223\363\257d\225\332\257rM:.\\\35\240El\22#"..., 1024, 0, NULL, NULL) = 141
20952 select(7, [6], NULL, NULL, {tv_sec=3, tv_usec=0}) = 1 (in [6], left {tv_sec=2, tv_usec=999994})
20952 recvfrom(6, "\4\0\0\206\0G\0\0\5\213\5\370?\341\316{\335\357\255l\353{]\351\7\335!9\262\30\317\243"..., 1024, 0, NULL, NULL) = 138
20952 select(7, [6], NULL, NULL, {tv_sec=3, tv_usec=0} <unfinished ...>
20953 <... nanosleep resumed>NULL)      = 0
20953 ioctl(5, SNDRV_PCM_IOCTL_WRITEI_FRAMES, 0x7f9c57ffe5c0) = 0
20953 nanosleep({tv_sec=0, tv_nsec=19479000}, NULL) = 0
20953 ioctl(5, SNDRV_PCM_IOCTL_WRITEI_FRAMES, 0x7f9c57ffe5c0) = 0
20953 nanosleep({tv_sec=0, tv_nsec=19481000},  <unfinished ...>
20952 <... select resumed>)             = 1 (in [6], left {tv_sec=2, tv_usec=972848})
20952 recvfrom(6, "\4\0\0\211\0H\0\0\5\232\5\370?\32$&\22[D\343n\214\356:\225J\302\231F?SS"..., 1024, 0, NULL, NULL) = 141
20952 select(7, [6], NULL, NULL, {tv_sec=3, tv_usec=0}) = 1 (in [6], left {tv_sec=2, tv_usec=991746})
Enter fullscreen mode Exit fullscreen mode

This is so far what I explored with strace and what I have been able to find about WO MIC client. However, this is only the Wi-fi technology eventually I will dig into the Wi-fi Direct, Bluetooth and USB options.

Unexpected Gain

so far, we have learned about asla-lib for sound processing and we have seen how the WO MIC manages its communication to transfer data. Also, we have learned how to use strace. These were all in my expectations and my goals. However, the permission problem brought to me something that I wasn't aware of: it is fuse.

Recall that in the networking section we got a permission problem when we tried to trace all the forks and the output of the program was like this:

//...
[pid 24160] openat(AT_FDCWD, "/dev/fuse", O_RDWR) = 6                                                                                                                    
//...
[pid 24160] openat(AT_FDCWD, "/etc/fuse.conf", O_RDONLY) = 7
[pid 24160] fstat(7, {st_mode=S_IFREG|0644, st_size=38, ...}) = 0
[pid 24160] read(7, "# mount_max = 1000\n# user_allow_"..., 4096) = 38
//...
[pid 24160] openat(AT_FDCWD, "/etc/mtab", O_RDONLY|O_CLOEXEC) = 7
[pid 24160] fstat(7, {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
[pid 24160] read(7, "sysfs /sys sysfs rw,seclabel,nos"..., 1024) = 1024
[pid 24160] read(7, "elatime,fd=29,pgrp=1,timeout=0,m"..., 1024) = 1024
[pid 24160] read(7, " 0\n/dev/mapper/fedora_localhost-"..., 1024) = 517
//...
[pid 24160] lstat("/tmp/.mount_miccliVllxVQ", {st_mode=S_IFDIR|0700, st_size=40, ...}) = 0
[pid 24160] getuid()                    = 1000
[pid 24160] chdir("/tmp/.mount_miccliVllxVQ") = 0
 //...
[pid 24160] mount("/dev/fuse", ".", "fuse", MS_RDONLY|MS_NOSUID|MS_NODEV, "fd=6,rootmode=40000,user_id=1000"...) = -1 EPERM (Operation not permitted)
[pid 24160] write(2, "fusermount: mount failed: Operat"..., 50fusermount: mount failed: Operation not permitted
) = 50
[pid 24160] close(6)                    = 0
[pid 24160] exit_group(1)
//...
[pid 24158] wait4(24160, NULL, 0, NULL) = 24160
[pid 24158] rmdir("/tmp/.mount_miccliVllxVQ") = 0
[pid 24158] munmap(0x7eff2a679000, 262432) = 0
[pid 24158] ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
[pid 24158] fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x2), ...}) = 0
[pid 24158] write(1, "\n", 1
)           = 1
[pid 24158] write(1, "Cannot mount AppImage, please ch"..., 53Cannot mount AppImage, please check your FUSE setup.
) = 53
[pid 24158] write(1, "You might still be able to extra"..., 172You might still be able to extract the contents of this AppImage 
if you run it with the --appimage-extract option. 
See https://github.com/AppImage/AppImageKit/wiki/FUSE 
) = 172
[pid 24158] write(1, "for more information\n", 21for more information
) = 21
[pid 24158] exit_group(0)
//...
Enter fullscreen mode Exit fullscreen mode

Turns out that fuse means filesystem in userspace and that it's an interface for Unix and Unix-like computer operating systems that lets non-privileged users create their own file systems without editing kernel code. This is achieved by running file system code in user space while the FUSE module provides only a "bridge" to the actual kernel interfaces.

fuse flow diagram

The above picture is a flow-chart diagram from Wikipedia that shows how FUSE works: Request from userspace to list files (ls -l /tmp/fuse) gets redirected by the Kernel through VFS to FUSE. FUSE then executes the registered handler program (./hello) and passes it the request (ls -l /tmp/fuse). The handler program returns a response back to FUSE which is then redirected to the userspace program that originally made the request.

In our case, WO MIC client setup a userspace file system at /tmp/.micclient... Though, I am not exactly sure what is its function.

Bonus: When I tweeted about fuse, Noah Watkins from Vectorized shared his implementation of a barebone, in-memory, FUSE file system leveraging the low-level FUSE interface. His Github repo is on my study list.

Conclusion

Through strace, I was able to gain a lot of insights and even to discover something I wasn't aware of. However, I only analyzed WO MIC when using Wi-fi as a sharing technology: we still have 3 other ways to look at. Moreover, I need to figure out the role of fuse in this.

Also, I have to answer the question of how to make the video stream available for my project: do I need to write a kernel module? is there a way in userspace? if there is, how much performance do I need to sacrifice?

Finally, I hope that you learned something from this: We discussed the reality of threads in Linux, the use of strace, and the audio APIs. For Fuse, maybe it will be the subject of a future article.

Discussion (0)

pic
Editor guide