pax_global_header 0000666 0000000 0000000 00000000064 13600441630 0014507 g ustar 00root root 0000000 0000000 52 comment=802bae4ea015e0cecb309b4f57dcbf4f60ac4b5d
termshark-2.0.3/ 0000775 0000000 0000000 00000000000 13600441630 0013511 5 ustar 00root root 0000000 0000000 termshark-2.0.3/.all-contributorsrc 0000664 0000000 0000000 00000021522 13600441630 0017344 0 ustar 00root root 0000000 0000000 {
"projectName": "termshark",
"projectOwner": "gcla",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"commitConvention": "none",
"contributors": [
{
"login": "pocc",
"name": "Ross Jacobs",
"avatar_url": "https://avatars0.githubusercontent.com/u/10995145?v=4",
"profile": "https://swit.sh",
"contributions": [
"code",
"bug",
"userTesting"
]
},
{
"login": "Hongarc",
"name": "Hongarc",
"avatar_url": "https://avatars1.githubusercontent.com/u/19208123?v=4",
"profile": "https://github.com/Hongarc",
"contributions": [
"doc"
]
},
{
"login": "zi0r",
"name": "Ryan Steinmetz",
"avatar_url": "https://avatars0.githubusercontent.com/u/1676702?v=4",
"profile": "https://github.com/zi0r",
"contributions": [
"platform"
]
},
{
"login": "NicolaiSoeborg",
"name": "Nicolai Søborg",
"avatar_url": "https://avatars2.githubusercontent.com/u/8722223?v=4",
"profile": "https://søb.org/",
"contributions": [
"platform"
]
},
{
"login": "QuLogic",
"name": "Elliott Sales de Andrade",
"avatar_url": "https://avatars2.githubusercontent.com/u/302469?v=4",
"profile": "https://qulogic.gitlab.io/",
"contributions": [
"code"
]
},
{
"login": "rski",
"name": "Romanos",
"avatar_url": "https://avatars2.githubusercontent.com/u/2960312?v=4",
"profile": "http://rski.github.io",
"contributions": [
"code"
]
},
{
"login": "denyspozniak",
"name": "Denys",
"avatar_url": "https://avatars0.githubusercontent.com/u/22612345?v=4",
"profile": "https://github.com/denyspozniak",
"contributions": [
"bug"
]
},
{
"login": "jerry73204",
"name": "jerry73204",
"avatar_url": "https://avatars1.githubusercontent.com/u/7629150?v=4",
"profile": "https://github.com/jerry73204",
"contributions": [
"platform"
]
},
{
"login": "Thann",
"name": "Jon Knapp",
"avatar_url": "https://avatars1.githubusercontent.com/u/578515?v=4",
"profile": "http://thann.github.com",
"contributions": [
"platform"
]
},
{
"login": "mharjac",
"name": "Mario Harjac",
"avatar_url": "https://avatars2.githubusercontent.com/u/2997453?v=4",
"profile": "https://github.com/mharjac",
"contributions": [
"platform"
]
},
{
"login": "abenson",
"name": "Andrew Benson",
"avatar_url": "https://avatars1.githubusercontent.com/u/227317?v=4",
"profile": "https://github.com/abenson",
"contributions": [
"bug"
]
},
{
"login": "sagis-tikal",
"name": "sagis-tikal",
"avatar_url": "https://avatars2.githubusercontent.com/u/46102019?v=4",
"profile": "https://github.com/sagis-tikal",
"contributions": [
"bug"
]
},
{
"login": "punkymaniac",
"name": "punkymaniac",
"avatar_url": "https://avatars2.githubusercontent.com/u/9916797?v=4",
"profile": "https://github.com/punkymaniac",
"contributions": [
"bug"
]
},
{
"login": "msenturk",
"name": "msenturk",
"avatar_url": "https://avatars3.githubusercontent.com/u/9482568?v=4",
"profile": "https://github.com/msenturk",
"contributions": [
"bug"
]
},
{
"login": "szuecs",
"name": "Sandor Szücs",
"avatar_url": "https://avatars3.githubusercontent.com/u/50872?v=4",
"profile": "https://github.com/szuecs",
"contributions": [
"bug"
]
},
{
"login": "dawidd6",
"name": "Dawid Dziurla",
"avatar_url": "https://avatars1.githubusercontent.com/u/9713907?v=4",
"profile": "https://github.com/dawidd6",
"contributions": [
"bug"
]
},
{
"login": "jJit0",
"name": "jJit0",
"avatar_url": "https://avatars1.githubusercontent.com/u/23521148?v=4",
"profile": "https://github.com/jJit0",
"contributions": [
"bug"
]
},
{
"login": "inzel",
"name": "inzel",
"avatar_url": "https://avatars3.githubusercontent.com/u/20195547?v=4",
"profile": "http://colinrogers001.com",
"contributions": [
"bug"
]
},
{
"login": "thejerrod",
"name": "thejerrod",
"avatar_url": "https://avatars1.githubusercontent.com/u/25254103?v=4",
"profile": "https://github.com/thejerrod",
"contributions": [
"ideas"
]
},
{
"login": "gdluca",
"name": "gdluca",
"avatar_url": "https://avatars3.githubusercontent.com/u/12004506?v=4",
"profile": "https://github.com/gdluca",
"contributions": [
"bug"
]
},
{
"login": "winpat",
"name": "Patrick Winter",
"avatar_url": "https://avatars2.githubusercontent.com/u/6016963?v=4",
"profile": "https://github.com/winpat",
"contributions": [
"platform"
]
},
{
"login": "RobertLarsen",
"name": "Robert Larsen",
"avatar_url": "https://avatars0.githubusercontent.com/u/795303?v=4",
"profile": "https://github.com/RobertLarsen",
"contributions": [
"ideas",
"userTesting"
]
},
{
"login": "mingrammer",
"name": "MinJae Kwon",
"avatar_url": "https://avatars0.githubusercontent.com/u/6178510?v=4",
"profile": "https://mingrammer.com",
"contributions": [
"bug"
]
},
{
"login": "the-c0d3r",
"name": "the-c0d3r",
"avatar_url": "https://avatars2.githubusercontent.com/u/4526565?v=4",
"profile": "https://github.com/the-c0d3r",
"contributions": [
"ideas"
]
},
{
"login": "gvanem",
"name": "Gisle Vanem",
"avatar_url": "https://avatars0.githubusercontent.com/u/945271?v=4",
"profile": "https://github.com/gvanem",
"contributions": [
"bug"
]
},
{
"login": "hook-s3c",
"name": "hook",
"avatar_url": "https://avatars1.githubusercontent.com/u/31825993?v=4",
"profile": "https://github.com/hook-s3c",
"contributions": [
"bug"
]
},
{
"login": "lennartkoopmann",
"name": "Lennart Koopmann",
"avatar_url": "https://avatars0.githubusercontent.com/u/35022?v=4",
"profile": "https://twitter.com/_lennart",
"contributions": [
"ideas"
]
},
{
"login": "ReK2Fernandez",
"name": "Fernandez, ReK2",
"avatar_url": "https://avatars1.githubusercontent.com/u/5316229?v=4",
"profile": "https://keybase.io/cfernandez",
"contributions": [
"bug"
]
},
{
"login": "mazball",
"name": "mazball",
"avatar_url": "https://avatars2.githubusercontent.com/u/22456251?v=4",
"profile": "https://github.com/mazball",
"contributions": [
"ideas"
]
},
{
"login": "wfailla",
"name": "wfailla",
"avatar_url": "https://avatars1.githubusercontent.com/u/5494665?v=4",
"profile": "https://github.com/wfailla",
"contributions": [
"ideas"
]
},
{
"login": "rongyi",
"name": "荣怡",
"avatar_url": "https://avatars3.githubusercontent.com/u/1034762?v=4",
"profile": "https://github.com/rongyi",
"contributions": [
"ideas"
]
},
{
"login": "thebyrdman-git",
"name": "thebyrdman-git",
"avatar_url": "https://avatars1.githubusercontent.com/u/55452713?v=4",
"profile": "https://github.com/thebyrdman-git",
"contributions": [
"bug"
]
},
{
"login": "cmosig",
"name": "Clemens Mosig",
"avatar_url": "https://avatars2.githubusercontent.com/u/32590522?v=4",
"profile": "http://www.mi.fu-berlin.de/en/inf/groups/ilab/members/mosig.html",
"contributions": [
"bug"
]
},
{
"login": "mrash",
"name": "Michael Rash",
"avatar_url": "https://avatars3.githubusercontent.com/u/380228?v=4",
"profile": "http://www.cipherdyne.org/",
"contributions": [
"userTesting"
]
},
{
"login": "joelparker",
"name": "joelparker",
"avatar_url": "https://avatars3.githubusercontent.com/u/136451?v=4",
"profile": "https://github.com/joelparker",
"contributions": [
"userTesting"
]
},
{
"login": "dragosmaftei",
"name": "Dragos Maftei",
"avatar_url": "https://avatars1.githubusercontent.com/u/15351028?v=4",
"profile": "https://github.com/dragosmaftei",
"contributions": [
"ideas"
]
}
],
"contributorsPerLine": 7
}
termshark-2.0.3/.github/ 0000775 0000000 0000000 00000000000 13600441630 0015051 5 ustar 00root root 0000000 0000000 termshark-2.0.3/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 13600441630 0017234 5 ustar 00root root 0000000 0000000 termshark-2.0.3/.github/ISSUE_TEMPLATE/bug_report.md 0000664 0000000 0000000 00000002217 13600441630 0021730 0 ustar 00root root 0000000 0000000 ---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
## Prerequisites
Please verify these before submitting an issue.
- [ ] I am running the latest versions of Termshark and Wireshark.
- [ ] I checked the [README](https://github.com/gcla/termshark) and [User Guide](https://github.com/gcla/termshark/blob/master/docs/UserGuide.md) and found no answer
- [ ] I searched [issues](https://github.com/gcla/termshark/issues?q=is%3Aissue) and this has not yet been filed
## Problem
### Current Behavior
Please describe the behavior you are seeing.
### Expected Behavior
Please describe the behavior you are expecting.
### Screenshots as applicable
* Provide screenshots of wireshark/tshark if that's what the expectation is based on.
* Provide screenshots of the problem state.
### Steps to Reproduce
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
## Context
Please provide the complete output of these commands:
* termshark -v (or termshark -vv if running from git/HEAD)
* termshark -v | cat
Please also provide any relevant information about your environment (OS, VM, pi,...)
termshark-2.0.3/.github/ISSUE_TEMPLATE/feature_request.md 0000664 0000000 0000000 00000001125 13600441630 0022760 0 ustar 00root root 0000000 0000000 ---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
### Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
### Describe the solution you'd like
A clear and concise description of what you want to happen.
### Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
### Additional context
Add any other context or screenshots about the feature request here.
termshark-2.0.3/.github/workflows/ 0000775 0000000 0000000 00000000000 13600441630 0017106 5 ustar 00root root 0000000 0000000 termshark-2.0.3/.github/workflows/go.yml 0000664 0000000 0000000 00000001405 13600441630 0020236 0 ustar 00root root 0000000 0000000 name: Go
on: [push]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.12
uses: actions/setup-go@v1
with:
go-version: 1.12
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v1
- name: Get dependencies
run: |
go get -v -t -d ./...
if [ -f Gopkg.toml ]; then
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure
fi
- name: Build
run: go build -v ./...
- name: Install tshark as prequisite for testing
run: sudo sh -c 'export DEBIAN_FRONTEND=noninteractive ; apt -y update && apt -y install tshark'
- name: Test
run: go test -v ./...
termshark-2.0.3/.gitignore 0000664 0000000 0000000 00000000070 13600441630 0015476 0 ustar 00root root 0000000 0000000 dist/
.vscode/
*~
/cmd/termshark/termshark
/typescript
termshark-2.0.3/.goreleaser.yml 0000664 0000000 0000000 00000001541 13600441630 0016443 0 ustar 00root root 0000000 0000000 # This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
before:
hooks:
builds:
- env:
- CGO_ENABLED=0
- GO111MODULE=on
main: ./cmd/termshark/termshark.go
goos:
- freebsd
- windows
- linux
- darwin
goarch:
- arm
- amd64
ignore:
- goos: darwin
goarch: arm
- goos: freebsd
goarch: arm
- goos: windows
goarch: arm
archives:
- replacements:
darwin: macOS
linux: linux
windows: windows
amd64: x64
wrap_in_directory: true
format_overrides:
- goos: windows
format: zip
files:
- none*
sign:
artifacts: checksum
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Env.TERMSHARK_GIT_DESCRIBE }}"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
termshark-2.0.3/.travis.yml 0000664 0000000 0000000 00000006003 13600441630 0015621 0 ustar 00root root 0000000 0000000 language: go
env:
global:
- GO111MODULE=on
- GOOGLE_APPLICATION_CREDENTIALS=/tmp/google.json
- secure: I+3P2j2pxXjaKBAemdC/NN8SozyG4BEcMXJqUPY4jmiJO6aVXHkEQUMHzk1hCzNNo2QA2ACz0ptSUKx9AIXlQuGmLPsbNGkCaLklT8+lLgwISbM+BGs3w8Bz777czbD1c2zHjb3k/90fgo0j+96Y+qpJOo3GAEBsz8Q47GQEZUFM05DPzd2Opj6fXkZBY0qaG5cXHF8UisgpYL4E6iYnXnPS7O3jPnyL28QnzYV9zsZGfPGWTSo0vOaO2l5gmPNZ33w+fnuN9pLayh6L8lLDYlwzAyzMUx9HTgp1qLknSzPmhSIqn1OebKVX5UiTWlfI/5mJj2TI0E/S+sFNTlkNR3r1qoNs09m7zRijxkQVKNj/Pzx5fbT5yYB6g7kPuD6Ag1NQb3TPocxOk/m4OHoU7m1kSl7IKr9LmNIGwEAzwIOPwxoHBXsSiZo4Zu4uD0pl6Jse7NTXCc5sPZagEwac70TZ0aPwuOwQDnwPU8PUUfDQwoYtuXkY8uv/lBki0y1JV8vVTNZoel6ZwJuJPB6IXn4ctI7c+MtX1eIg0zppkjaqnpi3eKbtpFGlbOAtquChd4LEr1O+Q5IJyPg7DFVfscH41owWR9T3sYXSpjVU8XKsAfjazS4Xx/brFItlvLjRQHdpZzng23EWBeYP9nCM5X5UZ2xEnrDJXefhrsHqWLw=
- secure: FLDaxfvg2nwSPo045nwSINI/84MgGwlku/ZgkvVbDQUNiIUc305UhaNCPuxq503Y2OJdw39wN3kkLQd08g4vYHXygBYTEr7k63D1Dq+0GltItTGPpMmhW2LCZFxMZ6QLVpLf/LGYY9DviT+zIXfDJekgvCcxT9cXTWxaKNAxENnvbUu/arnNg0liL9zyhoCIMiEJmJ/Pybq9MkL8mv+4i6DpzWTh6vGsO2OXfN52QQ/y4TqnJYsOfsRzKX30AzX+OvONUOhwt4j5AbQWn1VTFHWUz++GhY/LerJxOXYea2GqaY0NZ0+cvpUaMRpeAENJA778IQvRojMZnWgyRm7RAScHkJ0dT8CTuCkIXn/XN/r+bmTBdoB9C9DuDovo2Y86HgOeM1ZIRGjgNAja6oBJ7xi94m8TlsjoQuPl4eilB8Y5dX+tkUGTOZ90zWaP5xViAMIZpTiMS8ZWnHOGWB3M4EfmDDiaX6SOEcT7QFQMS/kj0RtBSZlXYcXEfvZnduW2eykfYeOVoDZoBZOiB93yVfhg5NrOeaKPe3XiiqlTygNUNRllO1SaYB317EQwSPAuFG1X2ocS0uHvVNT/DvoHDTmztEvJBB3aI+9jZbj1ZzVFtD525MaVGHCvXpCYjfKkhq3eMTsdTFOH2QlW2cf1UWhMPQyvMABr4C0Ro0ep/Kk=
branches:
only:
- gh-pages
- "/.*/"
git:
depth: false
go:
- 1.11.x
- 1.12.x
- 1.13.x
notifications:
email: true
before_install:
- openssl aes-256-cbc -K $encrypted_1286cb654632_key -iv $encrypted_1286cb654632_iv
-in configs/termshark-dd01307f2423.json.enc -out /tmp/google.json -d
- sudo apt-get install -y tshark
addons:
apt:
update: true
script:
- go test -v ./...
deploy:
- provider: script
skip_cleanup: true
script: bash scripts/do-release.sh
on:
all_branches: true
condition: "$TRAVIS_OS_NAME = linux && $TRAVIS_GO_VERSION = 1.13.x"
- provider: gcs
skip_cleanup: true
access_key_id: GOOGKKO2OYB5BE3XDNBJMDRK
secret_access_key:
secure: fKoBQYUVQLh5gZB/LE3jmv9atfuOh1oM3YZ+9nIvv39771WbI9nfcxpO9/LZldoWludUeamR8fGz8rqzP19g6aZK+tTAvBG8XZnbEIi7lCtrLmBY6FB7t9VkcA5oPyAd+ygnUF4BRh92gqGbYx4vdJdjPUSruiIq8HTw1eSdLCIjl2h+Rk4KSpmnPmZk1YtWI9TDkBG8dRLnIq16Rwb0ep5oeK1Omlvqr5PiuwUL3gJFfE7KWznjs2hv52yAXQY6J8vWyiifBnQI7wq3JvbcV0PkJjQ7oyCsIx56R4dMl+UjFynmA7PpOiZqCj+Rp4EcBmGnh1ofS7U9hDfvLiy9jbHNwPUBiYQj2aD/fykMmXvAUwqzvjxCqx1Ky/QweBcVUbs2jOwiCofl4gSrPYdB2dkcZ8I0x9BhhbByOIEN42MNdYGucaqAia8yP8fDgfofi7H3XW3dZCySFbpb2n2poskpDFqEiJdubtm4b15YxV3gKn0NppJVojlMinEFk0jQh0BIVSY6/30rygamhbTps7JR2rNRo/82QIStZ00ME8CgFBQuEU0tOvyX3ifJAsTJPy3g4/0p3SGLrc0iV9CW3ieboA0vXJQ7DkR+yAyKhvz0QoAZs3QiAxfJLFvKxe2L/UGlKqisRghW7WRiZn8AzQNuD0jhi63SqimA2q/HFe8=
bucket: termshark
acl: public-read
local-dir: dist
upload-dir: "$TRAVIS_COMMIT"
on:
repo: gcla/termshark
all_branches: true
condition: "$TRAVIS_OS_NAME = linux && $TRAVIS_GO_VERSION = 1.13.x"
termshark-2.0.3/CHANGELOG.md 0000664 0000000 0000000 00000005064 13600441630 0015327 0 ustar 00root root 0000000 0000000 # Changelog
## [2.0.3] - 2019-12-23
### Added
- Termshark now colorizes its packet list view by default, using the current Wireshark `colorfilter` rules.
- Termshark now supports tshark's `-t` option to specify the timestamp format in the packet list view.
### Changed
- Fixed a potential deadlock when reassembling very long streams.
## [2.0.2] - 2019-11-11
### Changed
- Internal Go API name changes that I didn't understand when I released termshark V2.
## [2.0.1] - 2019-11-10
### Changed
- Fix a mistake that caused a build break on homebrew.
## [2.0.0] - 2019-11-10
### Added
- Termshark supports TCP and UDP stream reassembly. See termshark's "Analysis" menu.
- By popular demand, termshark now has a dark mode! To turn on, run termshark and open the menu.
- Termshark can be configured to "auto-scroll" when reading live data (interface, fifo or stdin).
- Termshark uses less CPU, is less laggy under mouse input, and will use less than half as much RAM on larger pcaps.
- Termshark now supports piped input e.g.
```
$ tshark -i eth0 -w - | termshark
```
- Termshark now supports input from a fifo e.g.
```
1$ mkfifo myfifo
1$ tshark -i eth0 -w myfifo
2$ termshark -r myfifo
```
- Termshark supports running its UI on a different tty (make sure the tty doesn't have another process competing for reads and writes). This is useful
if you are feeding termshark with data from a process that writes to stderr, or if you want to see information displayed in the terminal that would
be covered up by termshark's UI e.g.
```
termshark -i eth0 --tty=/dev/pts/5
```
- Like Wireshark, termshark will now preserve the opened and closed structure of a packet as you move from one packet to the next. This lets the user
see differences between packets more easily.
- Termshark can now be installed for MacOS from [Homebrew](docs/FAQ.md#homebrew).
- Termshark now respects job control signals sent via the shell i.e. SIGTSTP and SIGCONT.
- Termshark on Windows no longer depends on the Cywgin tail command (and thus a Cygwin installation).
- The current packet capture source (file, interface, pipe, etc) is displayed in the termshark title bar.
- Termshark can be configured to eagerly load all pcap PDML data, rather than 1000 packets at a time.
### Changed
- You can now simply hit enter in the display filter widget to make its value take effect.
## [1.0.0] - 2019-04-17
- Initial release.
[Unreleased]: https://github.com/gcla/termshark/commpare/v2.0.0...HEAD
[1.0.0]: https://github.com/gcla/termshark/releases/tag/v1.0.0
[2.0.0]: https://github.com/gcla/termshark/releases/tag/v2.0.0
termshark-2.0.3/LICENSE 0000664 0000000 0000000 00000002070 13600441630 0014515 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2019 Graham Clark
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
termshark-2.0.3/README.md 0000664 0000000 0000000 00000034130 13600441630 0014771 0 ustar 00root root 0000000 0000000 [twitter-follow-url]: https://twitter.com/intent/follow?screen_name=termshark
[twitter-follow-img]: https://img.shields.io/twitter/follow/termshark.svg?style=social&label=Follow
# Termshark
A terminal user-interface for tshark, inspired by Wireshark.
**V2 is out now with stream reassembly, dark-mode and more! Here's the [ChangeLog](CHANGELOG.md#changelog).**

If you're debugging on a remote machine with a large pcap and no desire to scp it back to your desktop, termshark can help!
## Features
- Read pcap files or sniff live interfaces (where tshark is permitted).
- Inspect each packet using familiar Wireshark-inspired views
- Filter pcaps or live captures using Wireshark's display filters
- Reassemble and inspect TCP and UDP flows
- Copy ranges of packets to the clipboard from the terminal
- Written in Golang, compiles to a single executable on each platform - downloads available for Linux, macOS, FreeBSD, Android (termux) and Windows
tshark has many more features that termshark doesn't expose yet! See [What's Next](docs/FAQ.md#whats-next).
## Install Packages
Termshark is pre-packaged for the following platforms: [Arch Linux](docs/Packages.md#arch-linux), [Debian (unstable)](docs/Packages.md#debian), [FreeBSD](docs/Packages.md#freebsd), [Homebrew](docs/Packages.md#homebrew), [Kali Linux](docs/Packages.md#kali-linux), [NixOS](docs/Packages.md#nixos), [SnapCraft](docs/Packages.md#snapcraft), [Termux (Android)](docs/Packages.md#termux-android) and [Ubuntu](docs/Packages.md#ubuntu).
## Building
Termshark uses Go modules, so it's best to compile with Go 1.11 or higher. Set `GO111MODULE=on` then run:
```bash
go install github.com/gcla/termshark/v2/cmd/termshark
```
Then add ```~/go/bin/``` to your ```PATH```.
For all packet analysis, termshark depends on tshark from the Wireshark project. Make sure ```tshark``` is in your ```PATH```.
## Quick Start
Inspect a local pcap:
```bash
termshark -r test.pcap
```
Capture ping packets on interface ```eth0```:
```bash
termshark -i eth0 icmp
```
Run ```termshark -h``` for options.
## Downloads
Pre-compiled executables are available via [Github releases](https://github.com/gcla/termshark/releases). Or download the latest build from the master branch - [](https://travis-ci.org/gcla/termshark).
## User Guide
See the [termshark user guide](docs/UserGuide.md) (and my best guess at some [FAQs](docs/FAQ.md))
## Dependencies
Termshark depends on these open-source packages:
- [tshark](https://www.wireshark.org/docs/man-pages/tshark.html) - command-line network protocol analyzer, part of [Wireshark](https://wireshark.org)
- [tcell](https://github.com/gdamore/tcell) - a cell based terminal handling package, inspired by termbox
- [gowid](https://github.com/gcla/gowid) - compositional terminal UI widgets, inspired by [urwid](http://urwid.org), built on [tcell](https://github.com/gdamore/tcell)
Note that tshark is a run-time dependency, and must be in your ```PATH``` for termshark to function. Version 1.10.2 or higher is required (approx 2013).
## Contributors
Thanks to everyone that's contributed ports, patches and effort!
## Contact
- The author - Graham Clark (grclark@gmail.com) [![Follow on Twitter][twitter-follow-img]][twitter-follow-url]
## License
[](LICENSE)
termshark-2.0.3/cli/ 0000775 0000000 0000000 00000000000 13600441630 0014260 5 ustar 00root root 0000000 0000000 termshark-2.0.3/cli/all.go 0000664 0000000 0000000 00000006300 13600441630 0015356 0 ustar 00root root 0000000 0000000 // Copyright 2019 Graham Clark. All rights reserved. Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.
//
package cli
import "github.com/jessevdk/go-flags"
//======================================================================
// Used to determine if we should run tshark instead e.g. stdout is not a tty
type Tshark struct {
PassThru string `long:"pass-thru" default:"auto" optional:"true" optional-value:"true" choice:"yes" choice:"no" choice:"auto" choice:"true" choice:"false" description:"Run tshark instead (auto => if stdout is not a tty)."`
PrintIfaces bool `short:"D" optional:"true" optional-value:"true" description:"Print a list of the interfaces on which termshark can capture."`
TailSwitch
}
// Termshark's own command line arguments. Used if we don't pass through to tshark.
type Termshark struct {
Iface string `value-name:"" short:"i" description:"Interface to read."`
Pcap flags.Filename `value-name:"" short:"r" description:"Pcap file to read."`
DecodeAs []string `short:"d" description:"Specify dissection of layer type." value-name:"==,"`
PrintIfaces bool `short:"D" optional:"true" optional-value:"true" description:"Print a list of the interfaces on which termshark can capture."`
DisplayFilter string `short:"Y" description:"Apply display filter." value-name:""`
CaptureFilter string `short:"f" description:"Apply capture filter." value-name:""`
TimestampFormat string `short:"t" description:"Set the format of the packet timestamp printed in summary lines." choice:"a" choice:"ad" choice:"adoy" choice:"d" choice:"dd" choice:"e" choice:"r" choice:"u" choice:"ud" choice:"udoy" value-name:""`
PlatformSwitches
PassThru string `long:"pass-thru" default:"auto" optional:"true" optional-value:"true" choice:"auto" choice:"true" choice:"false" description:"Run tshark instead (auto => if stdout is not a tty)."`
LogTty bool `long:"log-tty" optional:"true" optional-value:"true" choice:"true" choice:"false" description:"Log to the terminal."`
Debug string `long:"debug" default:"false" hidden:"true" optional:"true" optional-value:"true" choice:"true" choice:"false" description:"Enable termshark debugging. See https://termshark.io/userguide."`
Help bool `long:"help" short:"h" optional:"true" optional-value:"true" description:"Show this help message."`
Version []bool `long:"version" short:"v" optional:"true" optional-value:"true" description:"Show version information."`
Args struct {
FilterOrFile string `value-name:"" description:"Filter (capture for iface, display for pcap), or pcap file to read."`
} `positional-args:"yes"`
}
// If args are passed through to tshark (e.g. stdout not a tty), then
// strip these out so tshark doesn't fail.
var TermsharkOnly = []string{"--pass-thru", "--log-tty", "--debug", "--tail"}
func FlagIsTrue(val string) bool {
return val == "true" || val == "yes"
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 78
// End:
termshark-2.0.3/cli/flags.go 0000664 0000000 0000000 00000001416 13600441630 0015705 0 ustar 00root root 0000000 0000000 // Copyright 2019 Graham Clark. All rights reserved. Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.
//
// +build !windows
package cli
//======================================================================
// Embedded in the CLI options struct.
type PlatformSwitches struct {
Tty string `long:"tty" description:"Display the UI on this terminal." value-name:""`
}
func (p PlatformSwitches) TtyValue() string {
return p.Tty
}
//======================================================================
type TailSwitch struct{}
func (t TailSwitch) TailFileValue() string {
return ""
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 78
// End:
termshark-2.0.3/cli/flags_windows.go 0000664 0000000 0000000 00000001425 13600441630 0017457 0 ustar 00root root 0000000 0000000 // Copyright 2019 Graham Clark. All rights reserved. Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.
package cli
import "github.com/jessevdk/go-flags"
//======================================================================
type PlatformSwitches struct{}
func (p PlatformSwitches) TtyValue() string {
return ""
}
//======================================================================
type TailSwitch struct {
Tail flags.Filename `value-name:"" long:"tail" hidden:"true" description:"Tail a file (private)."`
}
func (t TailSwitch) TailFileValue() string {
return string(t.Tail)
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 78
// End:
termshark-2.0.3/cmd/ 0000775 0000000 0000000 00000000000 13600441630 0014254 5 ustar 00root root 0000000 0000000 termshark-2.0.3/cmd/termshark/ 0000775 0000000 0000000 00000000000 13600441630 0016254 5 ustar 00root root 0000000 0000000 termshark-2.0.3/cmd/termshark/termshark.go 0000664 0000000 0000000 00000111127 13600441630 0020606 0 ustar 00root root 0000000 0000000 // Copyright 2019 Graham Clark. All rights reserved. Use of this source
// code is governed by the MIT license that can be found in the LICENSE
// file.
package main
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/blang/semver"
"github.com/gcla/gowid"
"github.com/gcla/termshark/v2"
"github.com/gcla/termshark/v2/cli"
"github.com/gcla/termshark/v2/pcap"
"github.com/gcla/termshark/v2/streams"
"github.com/gcla/termshark/v2/system"
"github.com/gcla/termshark/v2/tty"
"github.com/gcla/termshark/v2/ui"
"github.com/gcla/termshark/v2/widgets/filter"
"github.com/gdamore/tcell"
flags "github.com/jessevdk/go-flags"
"github.com/mattn/go-isatty"
"github.com/shibukawa/configdir"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
"gopkg.in/fsnotify.v1"
"net/http"
_ "net/http"
_ "net/http/pprof"
)
//======================================================================
// Run cmain() and afterwards make sure all goroutines stop, then exit with
// the correct exit code. Go's main() prototype does not provide for returning
// a value.
func main() {
// TODO - fix this later. goroutinewg is used every time a
// goroutine is started, to ensure we don't terminate until all are
// stopped. Any exception is a bug.
var ensureGoroutinesStopWG sync.WaitGroup
filter.Goroutinewg = &ensureGoroutinesStopWG
termshark.Goroutinewg = &ensureGoroutinesStopWG
pcap.Goroutinewg = &ensureGoroutinesStopWG
streams.Goroutinewg = &ensureGoroutinesStopWG
ui.Goroutinewg = &ensureGoroutinesStopWG
res := cmain()
ensureGoroutinesStopWG.Wait()
os.Exit(res)
}
func cmain() int {
startedSuccessfully := false // true if we reached the point where packets were received and the UI started.
uiSuspended := false // true if the UI was suspended due to SIGTSTP
sigChan := make(chan os.Signal, 100)
// SIGINT and SIGQUIT will arrive only via an external kill command,
// not the keyboard, because our line discipline is set up to pass
// ctrl-c and ctrl-\ to termshark as keypress events. But we slightly
// modify tcell's default and set up ctrl-z to invoke signal SIGTSTP
// on the foreground process group. An alternative would just be to
// recognize ctrl-z in termshark and issue a SIGSTOP to getpid() from
// termshark but this wouldn't stop other processes in a termshark
// pipeline e.g.
//
// tcpdump -i eth0 -w - | termshark -i -
//
// sending SIGSTOP to getpid() would not stop tcpdump. The expectation
// with bash job control is that all processes in the foreground
// process group will be suspended. I could send SIGSTOP to 0, to try
// to get all processes in the group, but if e.g. tcpdump is running
// as root and termshark is not, tcpdump will not be suspended. If
// instead I set the line discipline such that ctrl-z is not passed
// through but maps to SIGTSTP, then tcpdump will be stopped by ctrl-z
// via the shell by virtue of the fact that when all pipeline
// processes start running, they use the same tty line discipline.
system.RegisterForSignals(sigChan)
viper.SetConfigName("termshark") // no need to include file extension - looks for file called termshark.ini for example
stdConf := configdir.New("", "termshark")
dirs := stdConf.QueryFolders(configdir.Cache)
if err := dirs[0].CreateParentDir("dummy"); err != nil {
fmt.Printf("Warning: could not create cache dir: %v\n", err)
}
dirs = stdConf.QueryFolders(configdir.Global)
if err := dirs[0].CreateParentDir("dummy"); err != nil {
fmt.Printf("Warning: could not create config dir: %v\n", err)
}
viper.AddConfigPath(dirs[0].Path)
if f, err := os.OpenFile(filepath.Join(dirs[0].Path, "termshark.toml"), os.O_RDONLY|os.O_CREATE, 0666); err != nil {
fmt.Printf("Warning: could not create initial config file: %v\n", err)
} else {
f.Close()
}
err := viper.ReadInConfig()
if err != nil {
fmt.Println("Config file not found...")
}
// Used to determine if we should run tshark instead e.g. stdout is not a tty
var tsopts cli.Tshark
// Add help flag. This is no use for the user and we don't want to display
// help for this dummy set of flags designed to check for pass-thru to tshark - but
// if help is on, then we'll detect it, parse the flags as termshark, then
// display the intended help.
tsFlags := flags.NewParser(&tsopts, flags.IgnoreUnknown|flags.HelpFlag)
_, err = tsFlags.ParseArgs(os.Args)
passthru := true
if err != nil {
// If it's because of --help, then skip the tty check, and display termshark's help. This
// ensures we don't display a useless help, and further that you can pipe termshark's help
// into PAGER without invoking tshark.
if ferr, ok := err.(*flags.Error); ok && ferr.Type == flags.ErrHelp {
passthru = false
} else {
return 1
}
}
if tsopts.TailFileValue() != "" {
err = termshark.TailFile(tsopts.TailFileValue())
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v", err)
return 1
} else {
return 0
}
}
// Run after accessing the config so I can use the configured tshark binary, if there is one. I need that
// binary in the case that termshark is run where stdout is not a tty, in which case I exec tshark - but
// it makes sense to use the one in termshark.toml
if passthru &&
(cli.FlagIsTrue(tsopts.PassThru) ||
(tsopts.PassThru == "auto" && !isatty.IsTerminal(os.Stdout.Fd())) ||
tsopts.PrintIfaces) {
tsharkBin, kverr := termshark.TSharkPath()
if kverr != nil {
fmt.Fprintf(os.Stderr, kverr.KeyVals["msg"].(string))
return 1
}
args := []string{}
for _, arg := range os.Args[1:] {
if !termshark.StringInSlice(arg, cli.TermsharkOnly) && !termshark.StringIsArgPrefixOf(arg, cli.TermsharkOnly) {
args = append(args, arg)
}
}
args = append([]string{tsharkBin}, args...)
if runtime.GOOS != "windows" {
err = syscall.Exec(tsharkBin, args, os.Environ())
if err != nil {
fmt.Fprintf(os.Stderr, "Error execing tshark binary: %v\n", err)
return 1
}
} else {
// No exec() on windows
c := exec.Command(args[0], args[1:]...)
c.Stdout = os.Stdout
c.Stderr = os.Stderr
err = c.Start()
if err != nil {
fmt.Fprintf(os.Stderr, "Error starting tshark: %v\n", err)
return 1
}
err = c.Wait()
if err != nil {
fmt.Fprintf(os.Stderr, "Error waiting for tshark: %v\n", err)
return 1
}
return 0
}
}
// Termshark's own command line arguments. Used if we don't pass through to tshark.
var opts cli.Termshark
// Parse the args now as intended for termshark
tmFlags := flags.NewParser(&opts, flags.PassDoubleDash)
var filterArgs []string
filterArgs, err = tmFlags.Parse()
if err != nil {
fmt.Fprintf(os.Stderr, "Command-line error: %v\n\n", err)
ui.WriteHelp(tmFlags, os.Stderr)
return 1
}
if opts.Help {
ui.WriteHelp(tmFlags, os.Stdout)
return 0
}
if len(opts.Version) > 0 {
res := 0
ui.WriteVersion(tmFlags, os.Stdout)
if len(opts.Version) > 1 {
if tsharkBin, kverr := termshark.TSharkPath(); kverr != nil {
fmt.Fprintf(os.Stderr, kverr.KeyVals["msg"].(string))
res = 1
} else {
if ver, err := termshark.TSharkVersion(tsharkBin); err != nil {
fmt.Fprintf(os.Stderr, "Could not determine version of tshark from binary %s\n", tsharkBin)
res = 1
} else {
ui.WriteTsharkVersion(tmFlags, tsharkBin, ver, os.Stdout)
}
}
}
return res
}
usetty := opts.TtyValue()
if usetty != "" {
if ttyf, err := os.Open(usetty); err != nil {
fmt.Fprintf(os.Stderr, "Could not open terminal %s: %v.\n", usetty, err)
return 1
} else {
if !isatty.IsTerminal(ttyf.Fd()) {
fmt.Fprintf(os.Stderr, "%s is not a terminal.\n", usetty)
ttyf.Close()
return 1
}
ttyf.Close()
}
} else {
// Always override - in case the user has GOWID_TTY in a shell script (if they're
// using the gcla fork of tcell for another application).
usetty = "/dev/tty"
}
os.Setenv("GOWID_TTY", usetty)
// Allow the user to override the shell's TERM variable this way. Perhaps the user runs
// under screen/tmux, and the TERM variable doesn't reflect the fact their preferred
// terminal emumlator supports 256 colors.
termVar := termshark.ConfString("main.term", "")
if termVar != "" {
os.Setenv("TERM", termVar)
}
var psrc pcap.IPacketSource
defer func() {
if psrc != nil {
if remover, ok := psrc.(pcap.ISourceRemover); ok {
remover.Remove()
}
}
}()
pcapf := string(opts.Pcap)
// If no interface specified, and no pcap specified via -r, then we assume the first
// argument is a pcap file e.g. termshark foo.pcap
if pcapf == "" && opts.Iface == "" {
pcapf = string(opts.Args.FilterOrFile)
// `termshark` => `termshark -i 1` (livecapture on default interface if no args)
if pcapf == "" {
if termshark.IsTerminal(os.Stdin.Fd()) {
pfile, err := system.PickFile()
switch err {
case nil:
// We're on termux/android, and we were given a file. Not that termux
// makes a copy, so we ought to clean that up when termshark terminates.
psrc = pcap.TemporaryFileSource{pcap.FileSource{Filename: pfile}}
case system.NoPicker:
// We're not on termux/android. Treat like this:
// $ termshark
// # use network interface 1 - maps to
// # termshark -i 1
psrc = pcap.InterfaceSource{Iface: "1"}
default:
// We're on termux/android, but got an unexpected error.
//if err != termshark.NoPicker {
// !NoPicker means we could be on android/termux, but something else went wrong
if err = system.PickFileError(err.Error()); err != nil {
// Termux's toast ran into an error...! Maybe not installed?
fmt.Fprintf(os.Stderr, err.Error())
}
return 1
}
} else {
// $ cat foo.pcap | termshark
// # use stdin - maps to
// $ cat foo.pcap | termshark -r -
psrc = pcap.FileSource{Filename: "-"}
}
}
} else {
// Add it to filter args. Figure out later if they're capture or display.
filterArgs = append(filterArgs, opts.Args.FilterOrFile)
}
if pcapf != "" && opts.Iface != "" {
fmt.Fprintf(os.Stderr, "Please supply either a pcap or an interface.\n")
return 1
}
// Invariant: pcap != "" XOR opts.Iface != ""
if psrc == nil {
switch {
case pcapf != "":
psrc = pcap.FileSource{Filename: pcapf}
case opts.Iface != "":
psrc = pcap.InterfaceSource{Iface: opts.Iface}
}
}
// go-flags returns [""] when no extra args are provided, so I can't just
// test the length of this slice
argsFilter := strings.Join(filterArgs, " ")
// Work out capture filter afterwards because we need to determine first
// whether any potential first argument is intended as a pcap file instead of
// a capture filter.
captureFilter := opts.CaptureFilter
if psrc.IsInterface() && argsFilter != "" {
if opts.CaptureFilter != "" {
fmt.Fprintf(os.Stderr, "Two capture filters provided - '%s' and '%s' - please supply one only.\n", opts.CaptureFilter, argsFilter)
return 1
}
captureFilter = argsFilter
}
displayFilter := opts.DisplayFilter
// Validate supplied filters e.g. no capture filter when reading from file
if psrc.IsFile() {
if captureFilter != "" {
fmt.Fprintf(os.Stderr, "Cannot use a capture filter when reading from a pcap file - '%s' and '%s'.\n", captureFilter, pcapf)
return 1
}
if argsFilter != "" {
if opts.DisplayFilter != "" {
fmt.Fprintf(os.Stderr, "Two display filters provided - '%s' and '%s' - please supply one only.\n", opts.DisplayFilter, argsFilter)
return 1
}
displayFilter = argsFilter
}
}
// - means read from stdin. But termshark uses stdin for interacting with the UI. So if the
// iface is -, then dup stdin to a free descriptor, adjust iface to read from that descriptor,
// then open /dev/tty on stdin.
newinputfd := -1
if psrc.Name() == "-" {
if termshark.IsTerminal(os.Stdin.Fd()) {
fmt.Fprintf(os.Stderr, "Requested pcap source is %v (\"stdin\") but stdin is a tty.\n", opts.Iface)
fmt.Fprintf(os.Stderr, "Perhaps you intended to pipe packet input to termshark?\n")
return 1
}
if runtime.GOOS != "windows" {
newinputfd, err = system.MoveStdin()
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
return 1
}
psrc = pcap.PipeSource{Descriptor: fmt.Sprintf("/dev/fd/%d", newinputfd), Fd: newinputfd}
} else {
fmt.Fprintf(os.Stderr, "Sorry, termshark does not yet support piped input on Windows.\n")
return 1
}
}
// Better to do a command-line error if file supplied at command-line is not found. File
// won't be "-" at this point because above we switch to -i if input is "-"
// We haven't distinguished between file sources and fifo sources yet. So IsFile() will be true
// even if argument is a fifo
if psrc.IsFile() {
stat, err := os.Stat(psrc.Name())
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading file %s: %v.\n", psrc.Name(), err)
return 1
}
if stat.Mode()&os.ModeNamedPipe != 0 {
// If termshark was invoked with -r myfifo, switch to -i myfifo, which tshark uses. This
// also puts termshark in "interface" mode where it assumes the source is unbounded
// (e.g. a different spinner)
psrc = pcap.FifoSource{Filename: psrc.Name()}
} else {
if pcapffile, err := os.Open(psrc.Name()); err != nil {
// Do this up front before the UI starts to catch simple errors quickly - like
// the file not being readable. It's possible that tshark would be able to read
// it and the termshark user not, but unlikely.
fmt.Fprintf(os.Stderr, "Error reading file %s: %v.\n", psrc.Name(), err)
return 1
} else {
pcapffile.Close()
}
}
}
// Here we now have an accurate view of psrc - either file, fifo, pipe or interface
// Helpful to use logging when enumerating interfaces below, so do it first
if !opts.LogTty {
logfile := termshark.CacheFile("termshark.log")
logfd, err := os.OpenFile(logfile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not create log file %s: %v\n", logfile, err)
return 1
}
// Don't close it - just let the descriptor be closed at exit. logrus is used
// in many places, some outside of this main function, and closing results in
// an error often on freebsd.
//defer logfd.Close()
log.SetOutput(logfd)
}
if cli.FlagIsTrue(opts.Debug) {
for _, addr := range termshark.LocalIPs() {
log.Infof("Starting debug web server at http://%s:6060/debug/pprof/", addr)
}
go func() {
log.Println(http.ListenAndServe("0.0.0.0:6060", nil))
}()
}
for _, dir := range []string{termshark.CacheDir(), termshark.PcapDir()} {
if _, err = os.Stat(dir); os.IsNotExist(err) {
err = os.Mkdir(dir, 0777)
if err != nil {
fmt.Fprintf(os.Stderr, "Unexpected error making dir %s: %v", dir, err)
return 1
}
}
}
// Write this pcap out here because the color validation code later depends on empty.pcap
emptyPcap := termshark.CacheFile("empty.pcap")
if _, err := os.Stat(emptyPcap); os.IsNotExist(err) {
err = termshark.WriteEmptyPcap(emptyPcap)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not create dummy pcap %s: %v", emptyPcap, err)
return 1
}
}
tsharkBin, kverr := termshark.TSharkPath()
if kverr != nil {
fmt.Fprintf(os.Stderr, kverr.KeyVals["msg"].(string))
return 1
}
// Here, tsharkBin is a fully-qualified tshark binary that exists on the fs (absent race
// conditions...)
valids := termshark.ConfStrings("main.validated-tsharks")
if !termshark.StringInSlice(tsharkBin, valids) {
tver, err := termshark.TSharkVersion(tsharkBin)
if err != nil {
fmt.Fprintf(os.Stderr, "Could not determine tshark version: %v\n", err)
return 1
}
// This is the earliest version I could determine gives reliable results in termshark.
// tshark compiled against tag v1.10.1 doesn't populate the hex view.
mver, _ := semver.Make("1.10.2")
if tver.LT(mver) {
fmt.Fprintf(os.Stderr, "termshark will not operate correctly with a tshark older than %v (found %v)\n", mver, tver)
return 1
}
valids = append(valids, tsharkBin)
termshark.SetConf("main.validated-tsharks", valids)
}
// If the last tshark we used isn't the same as the current one, then remove the cached fields
// data structure so it can be regenerated.
if tsharkBin != termshark.ConfString("main.last-used-tshark", "") {
termshark.DeleteCachedFields()
}
// Write out the last-used tshark path. We do this to make the above fields cache be consistent
// with the tshark binary we're using.
termshark.SetConf("main.last-used-tshark", tsharkBin)
// Determine if the current binary supports color. Tshark will fail with an error if it's too old
// and you supply the --color flag. Assume true, and check if our current binary is not in the
// validate list.
ui.PacketColorsSupported = true
colorTsharks := termshark.ConfStrings("main.color-tsharks")
if !termshark.StringInSlice(tsharkBin, colorTsharks) {
ui.PacketColorsSupported, err = termshark.TSharkSupportsColor(tsharkBin)
if err != nil {
ui.PacketColorsSupported = false
} else {
colorTsharks = append(colorTsharks, tsharkBin)
termshark.SetConf("main.color-tsharks", colorTsharks)
}
}
// If opts.Iface is provided as a number, it's meant as the index of the interfaces as
// per the order returned by the OS. useIface will always be the name of the interface.
// See if the interface argument is an integer
checkInterfaceName := false
ifaceIdx := -1
if psrc.IsInterface() {
if i, err := strconv.Atoi(psrc.Name()); err == nil {
ifaceIdx = i
}
// If it's a fifo, then always treat is as a fifo and not a reference to something in tshark -D
if ifaceIdx != -1 {
// if the argument is an integer, then confirm it in the output of tshark -D
checkInterfaceName = true
} else if runtime.GOOS == "windows" {
// If we're on windows, then all interfaces - indices and names -
// will be in tshark -D, so confirm it there
checkInterfaceName = true
}
}
if checkInterfaceName {
ifaces, err := termshark.Interfaces()
if err != nil {
fmt.Fprintf(os.Stderr, "Could not enumerate network interfaces: %v\n", err)
return 1
}
gotit := false
var canonicalName string
for i, n := range ifaces { // ("NDIS_...", 7)
if i == psrc.Name() || n == ifaceIdx {
gotit = true
canonicalName = i
break
}
}
if gotit {
// Guaranteed that psrc.IsInterface() is true
// Use the canonical name e.g. "NDIS_...". Then the temporary filename will
// have a more meaningful name.
psrc = pcap.InterfaceSource{Iface: canonicalName}
} else {
fmt.Fprintf(os.Stderr, "Could not find network interface %s\n", psrc.Name())
return 1
}
}
watcher, err := termshark.NewConfigWatcher()
if err != nil {
fmt.Fprintf(os.Stderr, "Problem constructing config file watcher: %v", err)
return 1
}
defer watcher.Close()
//======================================================================
// If != "", then the name of the file to which packets are saved when read from an
// interface source. We can't just use the loader because the user might clear then load
// a recent pcap on top of the originally loaded packets.
ifacePcapFilename := ""
defer func() {
// if useIface != "" then we run dumpcap with the -i option - which
// means the packet source is either an interface, a pipe, or a
// fifo. In all cases, we save the packets to a file so that if a
// filter is applied, we can restart - and so that we preserve the
// capture at the end of running termshark.
if (psrc.IsInterface() || psrc.IsFifo() || psrc.IsPipe()) && startedSuccessfully {
fmt.Printf("Packets read from %s have been saved in %s\n", psrc.Name(), ifacePcapFilename)
}
}()
//======================================================================
ifaceExitCode := 0
var ifaceErr error
// This is deferred until after the app is Closed - otherwise messages written to stdout/stderr are
// swallowed by tcell.
defer func() {
if ifaceExitCode != 0 {
fmt.Fprintf(os.Stderr, "Cannot capture on device %s", psrc.Name())
if ifaceErr != nil {
fmt.Fprintf(os.Stderr, ": %v", ifaceErr)
}
fmt.Fprintf(os.Stderr, " (exit code %d)\n", ifaceExitCode)
if runtime.GOOS == "linux" && os.Geteuid() != 0 {
fmt.Fprintf(os.Stderr, "You might need: sudo setcap cap_net_raw,cap_net_admin+eip %s\n", termshark.DumpcapBin())
fmt.Fprintf(os.Stderr, "Or try running with sudo or as root.\n")
}
fmt.Fprintf(os.Stderr, "See https://termshark.io/no-root for more info.\n")
}
}()
// Initialize application state for dark mode and auto-scroll
ui.DarkMode = termshark.ConfBool("main.dark-mode", false)
ui.AutoScroll = termshark.ConfBool("main.auto-scroll", true)
ui.PacketColors = termshark.ConfBool("main.packet-colors", true)
// Set them up here so they have access to any command-line flags that
// need to be passed to the tshark commands used
pdmlArgs := termshark.ConfStringSlice("main.pdml-args", []string{})
psmlArgs := termshark.ConfStringSlice("main.psml-args", []string{})
if opts.TimestampFormat != "" {
psmlArgs = append(psmlArgs, "-t", opts.TimestampFormat)
}
tsharkArgs := termshark.ConfStringSlice("main.tshark-args", []string{})
if ui.PacketColors && !ui.PacketColorsSupported {
log.Warnf("Packet coloring is enabled, but %s does not support --color", tsharkBin)
ui.PacketColors = false
}
cacheSize := termshark.ConfInt("main.pcap-cache-size", 64)
bundleSize := termshark.ConfInt("main.pcap-bundle-size", 1000)
if bundleSize <= 0 {
maxBundleSize := 100000
log.Infof("Config specifies pcap-bundle-size as %d - setting to max (%d)", bundleSize, maxBundleSize)
bundleSize = maxBundleSize
}
ui.PcapScheduler = pcap.NewScheduler(
pcap.MakeCommands(opts.DecodeAs, tsharkArgs, pdmlArgs, psmlArgs, ui.PacketColors),
pcap.Options{
CacheSize: cacheSize,
PacketsPerLoad: bundleSize,
},
)
ui.Loader = ui.PcapScheduler.Loader
// Buffered because I might send something in this goroutine
startUIChan := make(chan struct{}, 1)
// Used to cancel the display of a message telling the user why there is no UI yet.
detectMsgChan := make(chan struct{}, 1)
var iwatcher *fsnotify.Watcher
var ifaceTmpFile string
if psrc.IsInterface() || psrc.IsFifo() || psrc.IsPipe() {
ifaceTmpFile = pcap.TempPcapFile(psrc.Name())
iwatcher, err = fsnotify.NewWatcher()
if err != nil {
fmt.Fprintf(os.Stderr, "Could not start filesystem watcher: %v\n", err)
return 1
}
defer func() {
if iwatcher != nil {
iwatcher.Close()
}
}()
// Don't start the UI until this file is created. When listening on a pipe,
// termshark will start a process similar to:
//
// dumpcap -i /dev/fd/3 -w ~/.cache/pcaps/tmp123.pcap
//
// dumpcap will not actually create that file until it has data to write to it.
// So we watch for the creation of that file, and until then, don't launch the UI.
// Then if the feeding process needs input first e.g. sudo tcpdump needs password,
// there won't be a conflict for reading /dev/tty.
//
if err := iwatcher.Add(termshark.PcapDir()); err != nil { //&& !os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Could not set up watcher for %s: %v\n", termshark.PcapDir(), err)
return 1
}
fmt.Printf("(The termshark UI will start when packets are detected...)\n")
} else {
// Start UI right away, reading from a file
startUIChan <- struct{}{}
}
// Do this before ui.Build. If ui.Build fails (e.g. bad TERM), then the filter will be left
// running, so we need the defer to be in effect here and not after the processing of ui.Build's
// error
defer func() {
if ui.FilterWidget != nil {
ui.FilterWidget.Close()
}
}()
var app *gowid.App
if app, err = ui.Build(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
// Tcell returns ExitError now because if its internal terminfo DB does not have
// a matching entry, it tries to build one with infocmp.
if _, ok := termshark.RootCause(err).(*exec.ExitError); ok {
fmt.Fprintf(os.Stderr, "Termshark could not recognize your terminal. Try changing $TERM.\n")
}
return 1
}
appRunner := app.Runner()
// Populate the filter widget initially - runs asynchronously
go ui.FilterWidget.UpdateCompletions(app)
ui.Running = false
validator := filter.Validator{
Invalid: &filter.ValidateCB{
App: app,
Fn: func(app gowid.IApp) {
if !ui.Running {
fmt.Fprintf(os.Stderr, "Invalid filter: %s\n", displayFilter)
ui.QuitRequestedChan <- struct{}{}
} else {
app.Run(gowid.RunFunction(func(app gowid.IApp) {
ui.OpenError(fmt.Sprintf("Invalid filter: %s", displayFilter), app)
}))
}
},
},
}
if psrc.IsFile() {
absfile, err := filepath.Abs(psrc.Name())
if err != nil {
fmt.Fprintf(os.Stderr, "Could not determine working directory: %v\n", err)
return 1
}
doit := func(app gowid.IApp) {
app.Run(gowid.RunFunction(func(app gowid.IApp) {
ui.FilterWidget.SetValue(displayFilter, app)
}))
ui.RequestLoadPcapWithCheck(absfile, displayFilter, app)
}
validator.Valid = &filter.ValidateCB{Fn: doit, App: app}
validator.Validate(displayFilter)
// no auto-scroll when reading a file
ui.AutoScroll = false
} else if psrc.IsInterface() || psrc.IsFifo() || psrc.IsPipe() {
// Verifies whether or not we will be able to read from the interface (hopefully)
ifaceExitCode = 0
if psrc.IsInterface() {
if ifaceExitCode, ifaceErr = termshark.RunForExitCode(termshark.DumpcapBin(), "-i", psrc.Name(), "-a", "duration:1", "-w", os.DevNull); ifaceExitCode != 0 {
return 1
}
}
ifValid := func(app gowid.IApp) {
app.Run(gowid.RunFunction(func(app gowid.IApp) {
ui.FilterWidget.SetValue(displayFilter, app)
}))
ifacePcapFilename = ifaceTmpFile
ui.PcapScheduler.RequestLoadInterface(psrc, captureFilter, displayFilter, ifaceTmpFile,
pcap.HandlerList{
ui.MakeSaveRecents("", displayFilter, app),
ui.MakePacketViewUpdater(app),
ui.MakeUpdateCurrentCaptureInTitle(app),
ui.ManageStreamCache{},
},
)
}
validator.Valid = &filter.ValidateCB{Fn: ifValid, App: app}
validator.Validate(displayFilter)
}
quitRequested := false
quitIssuedToApp := false
prevstate := ui.Loader.State()
var prev float64
progTicker := time.NewTicker(time.Duration(200) * time.Millisecond)
loaderPsmlFinChan := ui.Loader.PsmlFinishedChan
loaderIfaceFinChan := ui.Loader.IfaceFinishedChan
loaderPdmlFinChan := ui.Loader.Stage2FinishedChan
ctrlzLineDisc := tty.TerminalSignals{}
Loop:
for {
var opsChan <-chan pcap.RunFn
var tickChan <-chan time.Time
var emptyStructViewChan <-chan time.Time
var emptyHexViewChan <-chan time.Time
var psmlFinChan <-chan struct{}
var ifaceFinChan <-chan struct{}
var pdmlFinChan <-chan struct{}
var tmpPcapWatcherChan <-chan fsnotify.Event
var tmpPcapWatcherErrorsChan <-chan error
var tcellEvents <-chan tcell.Event
var afterRenderEvents <-chan gowid.IAfterRenderEvent
// For setting struct views empty. This isn't done as soon as a load is initiated because
// in the case we are loading from an interface and following new packets, we get an ugly
// blinking effect where the loading message is displayed, shortly followed by the struct or
// hex view which comes back from the pdml process (because the pdml process can only read
// up to the end of the currently seen packets, each time it has to start afresh from the
// beginning to get new packets). Waiting 500ms to display loading gives enough time, in
// practice,
if ui.EmptyStructViewTimer != nil {
emptyStructViewChan = ui.EmptyStructViewTimer.C
}
// For setting hex views empty
if ui.EmptyHexViewTimer != nil {
emptyHexViewChan = ui.EmptyHexViewTimer.C
}
// This should really be moved to a handler...
if ui.Loader.State() == 0 {
if prevstate != 0 {
// If the state has just switched to 0, it means no interface-reading process is
// running. That means we will no longer be reading from an interface or a fifo, so
// we point the loader at the file we wrote to the cache, and redirect all
// loads/filters to that now.
ui.Loader.TurnOffPipe()
app.Run(gowid.RunFunction(func(app gowid.IApp) {
ui.ClearProgressWidget(app)
ui.SetProgressDeterminate(app) // always switch back - for pdml (partial) loads of later data.
}))
// When the progress bar is enabled, track the previous percentage reached. This is
// so that I don't go "backwards" if I generate a progress value less than the last
// one, using the current algorithm (because it would be confusing to see it go
// backwards)
prev = 0.0
}
if quitRequested {
if ui.Running {
if !quitIssuedToApp {
app.Quit()
quitIssuedToApp = true // Avoid closing app twice - doubly-closed channel
}
} else {
// No UI so exit loop immediately
break Loop
}
}
}
if ui.Loader.State()&(pcap.LoadingPdml|pcap.LoadingPsml) != 0 {
tickChan = progTicker.C // progress is only enabled when a pcap may be loading
}
if ui.Loader.State()&pcap.LoadingPdml != 0 {
pdmlFinChan = loaderPdmlFinChan
}
if ui.Loader.State()&pcap.LoadingPsml != 0 {
psmlFinChan = loaderPsmlFinChan
}
if ui.Loader.State()&pcap.LoadingIface != 0 {
ifaceFinChan = loaderIfaceFinChan
}
// (User) operations are enabled by default (the test predicate is nil), or if the predicate returns true
// meaning the operation has reached its desired state. Only one operation can be in progress at a time.
if ui.PcapScheduler.IsEnabled() {
opsChan = ui.PcapScheduler.OperationsChan
}
// This tracks a temporary pcap file which is populated by dumpcap when termshark is
// reading from a fifo. If iwatcher is nil, it means we've got data and don't need to
// monitor any more.
if iwatcher != nil {
tmpPcapWatcherChan = iwatcher.Events
tmpPcapWatcherErrorsChan = iwatcher.Errors
}
// Only process tcell and gowid events if the UI is running.
if ui.Running {
tcellEvents = app.TCellEvents
}
afterRenderEvents = app.AfterRenderEvents
prevstate = ui.Loader.State()
select {
case we := <-tmpPcapWatcherChan:
if strings.Contains(we.Name, ifaceTmpFile) {
log.Infof("Pcap file %v has appeared - launching UI", we.Name)
iwatcher.Close()
iwatcher = nil
startUIChan <- struct{}{}
}
case err := <-tmpPcapWatcherErrorsChan:
fmt.Fprintf(os.Stderr, "Unexpected watcher error for %s: %v", ifaceTmpFile, err)
return 1
case <-startUIChan:
log.Infof("Launching termshark UI")
// Go to termshark UI view
if err = app.ActivateScreen(); err != nil {
fmt.Fprintf(os.Stderr, "Error starting UI: %v\n", err)
return 1
}
// Start tcell/gowid events for keys, etc
appRunner.Start()
// Reinstate our terminal overrides that allow ctrl-z
if err := ctrlzLineDisc.Set(); err != nil {
ui.OpenError(fmt.Sprintf("Unexpected error setting Ctrl-z handler: %v\n", err), app)
}
ui.Running = true
startedSuccessfully = true
close(startUIChan)
startUIChan = nil // make sure it's not triggered again
close(detectMsgChan) // don't display the message about waiting for the UI
defer func() {
// Do this to make sure the program quits quickly if quit is invoked
// mid-load. It's safe to call this if a pcap isn't being loaded.
//
// The regular stopLoadPcap will send a signal to pcapChan. But if app.quit
// is called, the main select{} loop will be broken, and nothing will listen
// to that channel. As a result, nothing stops a pcap load. This calls the
// context cancellation function right away
if ui.StreamLoader != nil {
ui.StreamLoader.SuppressErrors = true
}
ui.Loader.Close()
appRunner.Stop()
app.Close()
ui.Running = false
}()
case <-ui.QuitRequestedChan:
quitRequested = true
if ui.Loader.State() != 0 {
// We know we're not idle, so stop any load so the quit op happens quickly for the user. Quit
// will happen next time round because the quitRequested flag is checked.
ui.PcapScheduler.RequestStopLoad(ui.NoHandlers{})
}
case sig := <-sigChan:
if system.IsSigTSTP(sig) {
if ui.Running {
// Remove our terminal overrides that allow ctrl-z
ctrlzLineDisc.Restore()
// Stop tcell/gowid events for keys, etc
appRunner.Stop()
// Go back to terminal view
app.DeactivateScreen()
ui.Running = false
uiSuspended = true
} else {
log.Infof("UI not active - no terminal changes required.")
}
// This is not synchronous, but some time after calling this, we'll be suspended.
if err := system.StopMyself(); err != nil {
fmt.Fprintf(os.Stderr, "Unexpected error issuing SIGSTOP: %v\n", err)
return 1
}
} else if system.IsSigCont(sig) {
if uiSuspended {
// Go to termshark UI view
if err = app.ActivateScreen(); err != nil {
fmt.Fprintf(os.Stderr, "Error starting UI: %v\n", err)
return 1
}
// Start tcell/gowid events for keys, etc
appRunner.Start()
// Reinstate our terminal overrides that allow ctrl-z
if err := ctrlzLineDisc.Set(); err != nil {
ui.OpenError(fmt.Sprintf("Unexpected error setting Ctrl-z handler: %v\n", err), app)
}
ui.Running = true
uiSuspended = false
}
} else if system.IsSigUSR1(sig) {
if cli.FlagIsTrue(opts.Debug) {
termshark.ProfileCPUFor(20)
} else {
log.Infof("SIGUSR1 ignored by termshark - see the --debug flag")
}
} else if system.IsSigUSR2(sig) {
if cli.FlagIsTrue(opts.Debug) {
termshark.ProfileHeap()
} else {
log.Infof("SIGUSR2 ignored by termshark - see the --debug flag")
}
} else {
log.Infof("Starting termination via signal %v", sig)
ui.QuitRequestedChan <- struct{}{}
}
case fn := <-opsChan:
// We run the requested operation - because operations are now enabled, since this channel
// is listening - and the result tells us when operations can be re-enabled (i.e. the target
// state of the operation just started, for example). This means we can let an operation
// "complete", moving through a sequence of states to the final state, befpre accepting
// another request.
fn()
case <-ui.CacheRequestsChan:
ui.CacheRequests = pcap.ProcessPdmlRequests(ui.CacheRequests, ui.Loader,
struct {
ui.SetNewPdmlRequests
ui.SetStructWidgets
}{
ui.SetNewPdmlRequests{ui.PcapScheduler},
ui.SetStructWidgets{ui.Loader, app},
})
case <-ifaceFinChan:
// this state change only happens if the load from the interface is explicitly
// stopped by the user (e.g. the stop button). When the current data has come
// from loading from an interface, when stopped we still want to be able to filter
// on that data. So the load routines should treat it like a regular pcap
// (until the interface is started again). That means the psml reader should read
// from the file and not the fifo.
loaderIfaceFinChan = ui.Loader.IfaceFinishedChan
ui.Loader.SetState(ui.Loader.State() & ^pcap.LoadingIface)
case <-psmlFinChan:
if ui.Loader.LoadWasCancelled {
// Don't reset cancel state here. If, after stopping an interface load, I
// apply a filter, I need to know if the load was cancelled previously because
// if it was cancelled, I need to load from the temp pcap; if not cancelled,
// (meaning still running), then I just apply a new filter and have the pcap
// reader read from the fifo. Only do this if the user isn't quitting the app,
// otherwise it looks clumsy.
if !quitRequested {
app.Run(gowid.RunFunction(func(app gowid.IApp) {
ui.OpenError("Loading was cancelled.", app)
}))
}
}
// Reset
loaderPsmlFinChan = ui.Loader.PsmlFinishedChan
ui.Loader.SetState(ui.Loader.State() & ^pcap.LoadingPsml)
case <-pdmlFinChan:
loaderPdmlFinChan = ui.Loader.Stage2FinishedChan
ui.Loader.SetState(ui.Loader.State() & ^pcap.LoadingPdml)
case <-tickChan:
if system.HaveFdinfo && (ui.Loader.State() == pcap.LoadingPdml || !ui.Loader.ReadingFromFifo()) {
app.Run(gowid.RunFunction(func(app gowid.IApp) {
prev = ui.UpdateProgressBarForFile(ui.Loader, prev, app)
}))
} else {
app.Run(gowid.RunFunction(func(app gowid.IApp) {
ui.UpdateProgressBarForInterface(ui.Loader, app)
}))
}
case <-emptyStructViewChan:
app.Run(gowid.RunFunction(func(app gowid.IApp) {
ui.SetStructViewMissing(app)
ui.StopEmptyStructViewTimer()
}))
case <-emptyHexViewChan:
app.Run(gowid.RunFunction(func(app gowid.IApp) {
ui.SetHexViewMissing(app)
ui.StopEmptyHexViewTimer()
}))
case ev := <-tcellEvents:
app.HandleTCellEvent(ev, gowid.IgnoreUnhandledInput)
case ev, ok := <-afterRenderEvents:
// This means app.Quit() has been called, which closes the AfterRenderEvents
// channel - and then will accept no more events. select will then return
// nil on this channel - which we then use to break the loop
if !ok {
break Loop
}
app.RunThenRenderEvent(ev)
case <-watcher.ConfigChanged():
ui.UpdateRecentMenu(app)
}
}
return 0
}
//======================================================================
// Local Variables:
// mode: Go
// fill-column: 78
// End:
termshark-2.0.3/configs/ 0000775 0000000 0000000 00000000000 13600441630 0015141 5 ustar 00root root 0000000 0000000 termshark-2.0.3/configs/termshark-dd01307f2423.json.enc 0000664 0000000 0000000 00000004400 13600441630 0022237 0 ustar 00root root 0000000 0000000 c*ƄM3A4*
NeX*NӺO}t(y-hG=-M7vő 0d_)Lڑ
jőzBd^rS+Y8T"ħ/A{wQUC_D5͜6S١Apj 6H T~@E+ZOcNN~{G1VW:}@Ǘ)jyW[aJNeX5|xej:g`#MZpѧY c#Y3{RbiddE`Z$i9F]'A&U]*h#_IU"q;A"<@dNكeQV.`MZ :oђKPEv۞VAvEk:xV04)uc<%\kb]$@
#MuDW)O`s@Á6Em}*MP=1@%6įTZbKѣ^p?~}+c>QmXWA\rWPd ֙c>+khBu2)-I7ֻ'%+EQ:ڮŦ
eDz{E)gH0oSZYJ_ﹷWoPW>\m+:ZǪi
xaZ\Ɣ'PD=79PnNBH|~iBd|@"$e-d'Bf5;(lrE!II% s}㏆JRs#
TsW\(誡7UWY%~Yj
xrK`a],AWʯNsm'XUOIB͊2JeOZCiX"$k}/n5zI<mRp[
k2M⒣$Bc#)'+
:~+Wbm?C G$I9E+vc+pK-:F;s7 )t g6%|^wӪK7G/Eb$Nf d jD_'-l 9./w"jߜ+Xc=cp=HQQ
݁;|PFd7!:bw]i۟H,,|uA~Tt)vhnYM7Ӂmk="~t3L?8E
JXnk ra)7sAl]K9WsVfJoeX\")hU{4M:;}~ =B RSftnm6U%{B`nۆyFTOdA5uKOr,C)W$|Xbdy39qD<X.IN5KyCBM[)&De~ O134_܍P}=ىKTP\U֒*,9qkfYHZ a)+R.eIézY0 NQ :P *Ȟ ma
Ńtfz#,C
sdp7vskOv^![sªǝ V7t_78Paw#KbTxW:oY#^3O` ^)}UHN9VËH_GY~r6rH:5f'y""6(I"g(_غU|
zgq?Tkc7[B}WVץ.ssRy^(a/듺hGV,U
\\}n4ħZF_"g{|D#ΣLTxTj*